专注连锁门店会员营销管理系统
线上+线下+进销存+收银+会员2018-08-07 作者:秩名
微信官方已经开放了插件的共享,那么对于微信小程序开发来说,如何做到小程序打包插件呢?下面来看看文章。
如果你看过文档,相信你一定知道:
如果感觉无从着手,可以继续看看我是如何一步步开发并完善 mini-program-webpack-loader 来打包小程序的。
小程序有一个固定的套路,首先需要有一个 app.json 文件来定义所有的页面路径,然后每个页面有四个文件组成:.js,.json,.wxml,.wxss。所以我以 app.json 作为 webpack entry,当 webpack 执行插件的 apply 的时候,通过获取 entry 来知道小程序都有哪些页面。
这里使用了两个插件 MultiEntryPlugin,SingleEntryPlugin。为什么要这样做呢?因为 webpack 会根据你的 entry 配置(这里的 entry 不只是 webpack 配置里的 entry,import(), require.ensure() 都会生成一个 entry)来决定生成文件的个数,我们不希望把所有页面的 js 打包到一个文件,需要使用 SingleEntryPlugin 来生成一个新的 entry module;而那些静态资源,我们可以使用 MultiEntryPlugin 插件来处理,把这些文件作为一个 entry module 的依赖,在 loader 中配置 file-loader 即可把静态文件输出。伪代码如下:
const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin'); const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); class MiniPlugin { apply (compiler) { let options = compiler.options let context = compiler.rootContext let entry = options.entry let files = loadFiles(entry) let scripts = files.filter(file => /\.js$/.test(file)) let assets = files.filter(file => !/\.js$/.test(file)) new MultiEntryPlugin(context, assets, '__assets__').apply(compiler) scripts.forEach((file => { let fileName = relative(context, file).replace(extname(file), ''); new SingleEntryPlugin(context, file, fileName).apply(compiler); }) } } 复制代码
当然,如果像上面那样做,你会发现最后会多出一个 main.js,xxx.js(使用 MultiEntryPlugin 时填的名字),main.js 对应的是配置的 entry 生成的文件,xxx.js 则是 MultiEntryPlugin 生成的。这些文件不是我们需要的,所以需要去掉他。如果熟悉 webpack 文档,我们有很多地方可以修改最终打包出来的文件,如 compiler 的 emit 事件,compilation 的 optimizeChunks 相关的事件都可以实现。其本质上就是去修改 compilation.assets 对象。
在 mini-program-webpack-loader 中就使用了 emit 事件来处理这种不需要输出的内容。
小程序打包当然没这么简单,还得支持wxml、wxss、wxs和自定义组件的引用,所以这个时候就需要一个 loader 来完成了,loader 需要做的事情也非常简单 —— 解析依赖的文件,如 .wxml 需要解析 import 组件的 src,wxs 的 src,.wxss 需要解析 @import,wxs 的 require,最后在 loader 中使用 loadModule 方法添加即可。自定义组件一开始在 add entry 步骤的时候直接获取了,所以不需要 loader 来完成。
这样做也没什么问题,可是开发体验是比较差的,如再添加一个自定义组件,一个页面,webpack 是无感知的,所以需要在页面中的 .json 发生改变时检查是不是新增了自定义组件或者新增了页面。这个时候遇到一个问题,自定义组件的 js 是不能通过 addModule 的方式来添加的,因为自定义组件的 js 必须作为独立的入口文件。在 loader 中是做不了,所以尝试把文件传到 plugin 中(因为 plugin 先于 loader 执行,所以是可以建立 loader 和 plugin 通信的)。
简单粗暴的方式:
// loader.js class MiniLoader {} module.exports = function (content) { new MiniLoader(this, content) } module.exports.$applyPluginInstance = function (plugin) { MiniLoader.prototype.$plugin = plugin } // plugin.js const loader = require('./loader') class MiniPlugin { apply (compiler) { loader.$applyPluginInstance(this); } } 复制代码
但是...。文件是传到 plugin 了,可是再使用 SingleEntryPlugin 时你会发现,没效果。因为在 compiler make 之后 webpack 已经不能感知新的 module 添加了,所以是没有用的,这个时候就需要根据文档猜,怎么样才能让 webpack 感知到新的 module,根据文档中的事件做关键字查询,可以发现在编译完成的时候会调用 compilation needAdditionalPass 事件钩子:
this.emitAssets(compilation, err => { if (err) return finalCallback(err); if (compilation.hooks.needAdditionalPass.call()) { compilation.needAdditionalPass = true; const stats = new Stats(compilation); stats.startTime = startTime; stats.endTime = Date.now(); this.hooks.done.callAsync(stats, err => { if (err) return finalCallback(err); this.hooks.additionalPass.callAsync(err => { if (err) return finalCallback(err); this.compile(onCompiled); }); }); return; } this.emitRecords(err => { if (err) return finalCallback(err); const stats = new Stats(compilation); stats.startTime = startTime; stats.endTime = Date.now(); this.hooks.done.callAsync(stats, err => { if (err) return finalCallback(err); return finalCallback(null, stats); }); }); }); 复制代码
如果在这个事件钩子返回一个 true 值,则可以使 webpack 调用 compiler additionalPass 事件钩子,尝试在这里添加文件,果然是可以的。
当然,小程序打包还有些不同的地方,比如分包,如何用好 splitchunk,就不在啰嗦了,当你开始以后你会发现有很多的方法来实现想要的效果。
插件开发到这里差不多了,总的来说,webpack 就是变着花样的回调,当你知道每个回调该做什么的时候,webpack 用起来就轻松了。明显我不知道,因为在开发过程中遇到了一些问题。
1.如何在小程序代码中支持 resolve alias,node_modules?
既然是工具,当然需要做更多的事情,有赞的小程序那么复杂,如果支持 resolve alias,node_modules 可以使得项目更方便维护,或许你会说这不是 webpack 最基本的功能吗,不是的,我们当然是希望可以在任何文件中使用 alias,node_modules 支持的不仅仅是 js。当然这样做就意味着事情将变得复杂,首先就是获取文件路径,必须是异步的,因为在 webpack 4 中 resolve 不再支持 sync。其次就是小程序的目录名不能是 node_modules,这时就需要一种计算相对路径的规则,还是相对打包输出的,而不是相对当前项目目录。
2.多个小程序项目的合并
有赞从小程序来讲,有微商城版,有零售版,以及公共版,其中大多基础功能,业务都是相同的,当然不能再每个小程序在开发一次,所以这个工具具备合并多个小程序当然是必须的。这样的合并稍微又要比从 node_modules 中取文件复杂一些,因为需要保证多个小程序合并后的页面是正确的,而且要保证路径不变。
这两个问题的最终的解决方案既是以 webpack rootContext 的 src 目录为基准目录,以该目录所在路径计算打包文件的绝对路径,然后根据入口文件的 app.json 所在目录的路径计算出最终输出路径。
exports.getDistPath = (compilerContext, entryContexts) => { /** * webpack 以 config 所在目录的 src 为打包入口 * 所以可以根据该目录追溯源文件地址 */ return (path) => { let fullPath = compilerContext let npmReg = /node_modules/g let pDirReg = /^[_|\.\.]\//g if (isAbsolute(path)) { fullPath = path } else { // 相对路径:webpack 最后生成的路径,打包入口外的文件都以 '_' 表示上级目录 while (pDirReg.test(path)) { path = path.substr(pDirReg.lastIndex) fullPath = join(fullPath, '../') } if (fullPath !== compilerContext) { fullPath = join(fullPath, path) } } // 根据 entry 中定义的 json 文件目录获取打包后所在目录,如果不能获取就返回原路径 let contextReg = new RegExp(entryContexts.join('|'), 'g') if (fullPath !== compilerContext && contextReg.exec(fullPath)) { path = fullPath.substr(contextReg.lastIndex + 1) console.assert(!npmReg.test(path), `文件${path}路径错误:不应该还包含 node_modules`) } /** * 如果有 node_modules 字符串,则去模块名称 * 如果 app.json 在 node_modules 中,那 path 不应该包含 node_modules */ if (npmReg.test(path)) { path = path.substr(npmReg.lastIndex + 1) } return path } } 复制代码
3.如何把子包单独依赖的内容打包到子包内
解决这个问题的方法是通过 optimizeChunks 事件,在每个 chunk 的依赖的 module 中添加这个 chunk 的入口文件,然后在 splitChunk 的 test 配置中检查 module 被依赖的数量。如果只有一个,并且是被子包依赖,则打包到子包内。
4.webpack 支持单文件失败
这是一个未解决的问题,当尝试使用 webpack 来支持单文件的时候,好像没那么方便:
线上+线下+进销存+收银+会员门店零售管理好帮手
门店能实现快速收银,提升门店经营效率。
系统自带进销存管理,更好管理库存销售。
配备收银硬件一站式整体服务。
收 银 会员管理 库存管理 商品管理 交接班 数据报表 营销促单 售后处理 多种收款
获取报价1、本网站发布的该篇文章,目的在于分享电商知识及传递、交流相关电商信息,以便您学习或了解电商知识,请您不要用于其他用途;
2、该篇文章中所涉及的商标、标识的商品/服务并非来源于本网站,更非本网站提供,与本网站无关,系他人的商品或服务,本网站对于该类商标、标识不拥有任何权利;
3、本网站不对该篇文章中所涉及的商标、标识的商品/服务作任何明示或暗示的保证或担保;
4、本网站不对文章中所涉及的内容真实性、准确性、可靠性负责,仅系客观性描述,如您需要了解该类商品/服务详细的资讯,请您直接与该类商品/服务的提供者联系。
推荐文章
最新消息总结,开发一个小程序我们可以选择的第三方框架有5种,大家比较熟悉的有美团小程序框架mpvue,还有组件化框架等...
最近在调研小程序的分享能力,本篇文档主要是调研小程序关于分享方面的玩法,目的是学习小程序在项目应用以及玩法链上...
许多人在使用小程序,会有一个很麻烦的事情,就是使用过小程序之后,会不断的发送自己的定位消息,那么这个小程序的...
一款小程序,很多时候需要调用内置的照相机,实现拍照并图片上传,那么要如何实现呢?...
积分商城平台搭建全解析 在数字化营销的浪潮中,积分商城平台已成为众多企业提升用户粘性、促进消费转化的重要工具。...
公众号积分商城搭建百科 在数字化营销蓬勃发展的当下,公众号积分商城已成为企业和品牌提升用户活跃度、增强用户粘性...
积分商城搭建流程全解析 在数字化营销浪潮中,积分商城已成为众多企业提升用户粘性、促进消费转化的得力工具。搭建一...
积分商城搭建需要多少钱 在当今数字化商业环境中,积分商城作为一种有效的用户激励和营销工具,越来越受到企业的青睐...
积分商城小程序百科 积分商城小程序是一种基于移动互联网平台的应用程序,它依托于微信、支付宝等大型平台,为商家和...
积分商城搭建步骤百科 积分商城作为一种激励用户消费和提高用户粘性的工具,在企业营销策略中占据重要地位。它通过积...