Open CommanderXL opened 4 years ago
不同于 Vue 借助 webpack 是将 Vue 单文件最终打包成单独的 js chunk 文件。而小程序的规范是每个页面/组件需要对应的 wxml/js/wxss/json 4个文件。因为 mpx 使用单文件的方式去组织代码,所以在编译环节所需要做的工作之一就是将 mpx 单文件当中不同 block 的内容拆解到对应文件类型当中。在动态入口编译的小节里面我们了解到 mpx 会分析每个 mpx 文件的引用依赖,从而去给这个文件创建一个 entry 依赖(SingleEntryPlugin)并加入到 webpack 的编译流程当中。我们还是继续看下 mpx loader 对于 mpx 单文件初步编译转化后的内容:
/* script */ export * from "!!babel-loader!../../node_modules/@mpxjs/webpack-plugin/lib/selector?type=script&index=0!./list.mpx" /* styles */ require("!!../../node_modules/@mpxjs/webpack-plugin/lib/extractor?type=styles&index=0!../../node_modules/@mpxjs/webpack-plugin/lib/wxss/loader?root=&importLoaders=1&extract=true!../../node_modules/@mpxjs/webpack-plugin/lib/style-compiler/index?{\"id\":\"2271575d\",\"scoped\":false,\"sourceMap\":false,\"transRpx\":{\"mode\":\"only\",\"comment\":\"use rpx\",\"include\":\"/Users/XRene/demo/mpx-demo-source/src\"}}!stylus-loader!../../node_modules/@mpxjs/webpack-plugin/lib/selector?type=styles&index=0!./list.mpx") /* json */ require("!!../../node_modules/@mpxjs/webpack-plugin/lib/extractor?type=json&index=0!../../node_modules/@mpxjs/webpack-plugin/lib/json-compiler/index?root=!../../node_modules/@mpxjs/webpack-plugin/lib/selector?type=json&index=0!./list.mpx") /* template */ require("!!../../node_modules/@mpxjs/webpack-plugin/lib/extractor?type=template&index=0!../../node_modules/@mpxjs/webpack-plugin/lib/wxml/wxml-loader?root=!../../node_modules/@mpxjs/webpack-plugin/lib/template-compiler/index?{\"usingComponents\":[],\"hasScoped\":false,\"isNative\":false,\"moduleId\":\"2271575d\"}!../../node_modules/@mpxjs/webpack-plugin/lib/selector?type=template&index=0!./list.mpx")
接下来可以看下 styles/json/template 这3个 block 的处理流程是什么样。
首先来看下 json block 的处理流程:list.mpx -> json-compiler -> extractor。第一个阶段 list.mpx 文件经由 json-compiler 的处理流程在前面的章节已经讲过,主要就是分析依赖增加动态入口的编译过程。当所有的依赖分析完后,调用 json-compiler loader 的异步回调函数:
list.mpx -> json-compiler -> extractor
// lib/json-compiler/index.js module.exports = function (content) { ... const nativeCallback = this.async() ... let callbacked = false const callback = (err, processOutput) => { checkEntryDeps(() => { callbacked = true if (err) return nativeCallback(err) let output = `var json = ${JSON.stringify(json, null, 2)};\n` if (processOutput) output = processOutput(output) output += `module.exports = JSON.stringify(json, null, 2);\n` nativeCallback(null, output) }) } }
这里我们可以看到经由 json-compiler 处理后,通过nativeCallback方法传入下一个 loader 的文本内容形如:
nativeCallback
var json = { "usingComponents": { "list": "/components/list397512ea/list" } } module.exports = JSON.stringify(json, null, 2)
即这段文本内容会传递到下一个 loader 内部进行处理,即 extractor。接下来我们来看下 extractor 里面主要是实现了哪些功能:
// lib/extractor.js module.exports = function (content) { ... const contentLoader = normalize.lib('content-loader') let request = `!!${contentLoader}?${JSON.stringify(options)}!${this.resource}` // 构建一个新的 resource,且这个 resource 只需要经过 content-loader let resultSource = defaultResultSource const childFilename = 'extractor-filename' const outputOptions = { filename: childFilename } // 创建一个 child compiler const childCompiler = mainCompilation.createChildCompiler(request, outputOptions, [ new NodeTemplatePlugin(outputOptions), new LibraryTemplatePlugin(null, 'commonjs2'), // 最终输出的 chunk 内容遵循 commonjs 规范的可执行的模块代码 module.exports = (function(modules) {})([modules]) new NodeTargetPlugin(), new SingleEntryPlugin(this.context, request, resourcePath), new LimitChunkCountPlugin({ maxChunks: 1 }) ]) ... childCompiler.hooks.thisCompilation.tap('MpxWebpackPlugin ', (compilation) => { // 创建 loaderContext 时触发的 hook,在这个 hook 触发的时候,将原本从 json-compiler 传递过来的 content 内容挂载至 loaderContext.__mpx__ 属性上面以供接下来的 content -loader 来进行使用 compilation.hooks.normalModuleLoader.tap('MpxWebpackPlugin', (loaderContext, module) => { // 传递编译结果,子编译器进入content-loader后直接输出 loaderContext.__mpx__ = { content, fileDependencies: this.getDependencies(), contextDependencies: this.getContextDependencies() } }) }) let source childCompiler.hooks.afterCompile.tapAsync('MpxWebpackPlugin', (compilation, callback) => { // 这里 afterCompile 产出的 assets 的代码当中是包含 webpack runtime bootstrap 的代码,不过需要注意的是这个 source 模块的产出形式 // 因为使用了 new LibraryTemplatePlugin(null, 'commonjs2') 等插件。所以产出的 source 是可以在 node 环境下执行的 module // 因为在 loaderContext 上部署了 exec 方法,即可以直接执行 commonjs 规范的 module 代码,这样就最终完成了 mpx 单文件当中不同模块的抽离工作 source = compilation.assets[childFilename] && compilation.assets[childFilename].source() // Remove all chunk assets compilation.chunks.forEach((chunk) => { chunk.files.forEach((file) => { delete compilation.assets[file] }) }) callback() }) childCompiler.runAsChild((err, entries, compilation) => { ... try { // exec 是 loaderContext 上提供的一个方法,在其内部会构建原生的 node.js module,并执行这个 module 的代码 // 执行这个 module 代码后获取的内容就是通过 module.exports 导出的内容 let text = this.exec(source, request) if (Array.isArray(text)) { text = text.map((item) => { return item[1] }).join('\n') } let extracted = extract(text, options.type, resourcePath, +options.index, selfResourcePath) if (extracted) { resultSource = `module.exports = __webpack_public_path__ + ${JSON.stringify(extracted)};` } } catch (err) { return nativeCallback(err) } if (resultSource) { nativeCallback(null, resultSource) } else { nativeCallback() } }) }
稍微总结下上面的处理流程:
module.exports
所以上面的示例 demo 最终会输出一个 json 文件,里面包含的内容即为:
{ "usingComponents": { "list": "/components/list397512ea/list" } }
不同于 Vue 借助 webpack 是将 Vue 单文件最终打包成单独的 js chunk 文件。而小程序的规范是每个页面/组件需要对应的 wxml/js/wxss/json 4个文件。因为 mpx 使用单文件的方式去组织代码,所以在编译环节所需要做的工作之一就是将 mpx 单文件当中不同 block 的内容拆解到对应文件类型当中。在动态入口编译的小节里面我们了解到 mpx 会分析每个 mpx 文件的引用依赖,从而去给这个文件创建一个 entry 依赖(SingleEntryPlugin)并加入到 webpack 的编译流程当中。我们还是继续看下 mpx loader 对于 mpx 单文件初步编译转化后的内容:
接下来可以看下 styles/json/template 这3个 block 的处理流程是什么样。
首先来看下 json block 的处理流程:
list.mpx -> json-compiler -> extractor
。第一个阶段 list.mpx 文件经由 json-compiler 的处理流程在前面的章节已经讲过,主要就是分析依赖增加动态入口的编译过程。当所有的依赖分析完后,调用 json-compiler loader 的异步回调函数:这里我们可以看到经由 json-compiler 处理后,通过
nativeCallback
方法传入下一个 loader 的文本内容形如:即这段文本内容会传递到下一个 loader 内部进行处理,即 extractor。接下来我们来看下 extractor 里面主要是实现了哪些功能:
稍微总结下上面的处理流程:
module.exports
导出的内容。所以上面的示例 demo 最终会输出一个 json 文件,里面包含的内容即为: