Taro 是一套遵循 React 语法规范的多端开发解决方案。支持用 React 的开发方式编写一次代码,生成能运行在微信小程序和H5。
背景
事情的起源是因为需要对生成的H5应用高度个性化的 Webpack 配置,而 Taro 默认只提供了 webpack-chain 的方式来修改webpack,导致无法与组内通用的一份webpack config 直接合并(不能用webpack-merge)。不得已之下只好从taro源码寻求解决方案。
从入口开始
从运行的命令入手。查看 package.json, 可以看到与h5相关的是这两个命令:
1 2
| "dev:h5": "npm run build:h5 -- --watch", "build:h5": "taro build --type h5",
|
于是我们去看看 taro build 到底干了什么。方法当然是先找到 taro 命令对应的文件,在Mac上直接执行 which taro 即可(windows下执行where taro)
可以看到是在全局的node_modules下
1 2
| /usr/local/bin/node_modules/@tarojs/cli/bin
|
马上就能发现同目录下有taro-build文件,打开看到里面的核心方法 build
1 2 3 4 5 6 7
| const build = require('../src/build') function buildForH5 (buildConfig) { require('./h5').build(buildConfig) }
|
看来关键就是src/h5.js里面的代码
H5的“编译”过程
.h5.js里的build方法很简单,就两个关键方法:buildTemp, buildDist
1 2 3 4 5 6 7 8 9
| async function build (buildConfig) { process.env.TARO_ENV = Util.BUILD_TYPES.H5 await clean() await buildTemp(buildConfig) await buildDist(buildConfig) if (buildConfig.watch) { watchFiles() } }
|
buildDist这个比较好理解,就是常规的webpack打包过程。那`buildTemp``又干了什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function buildTemp () { fs.ensureDirSync(tempPath) return new Promise((resolve, reject) => { klaw(sourceDir) .on('data', file => { const relativePath = path.relative(appPath, file.path) if (!file.stats.isDirectory()) { Util.printLog(Util.pocessTypeEnum.CREATE, '发现文件', relativePath) processFiles(file.path) } }) .on('end', () => { resolve() }) }) }
|
可以发现, buildTemp是在遍历源码,然后生成一份平台相关的代码(抹平API差异,根据平台引入不同的包),并放在项目下的.temp文件夹。
这么看来,我们似乎直接对 .temp 文件夹执行 webpack就能够进行自定义的webpack构建了!
就差一点点
当我兴高采烈地执行完webpack,打开页面后,看到的是一个两倍大小的页面。样式出问题了,看来webpack配置还差了一些东西,果断把taro生成的webpack配置打印出来看看:
1 2 3 4 5 6 7 8 9 10
| /config/index.js const config = { h5: { webpackChain(chain) { console.log(chain.toString()) } } }
|
对比发现其实是少了postcss的loader。另外需要注意的是,taro还支持配置designWidth,deviceRatio,为了保证多端样式统一,我们的postcss配置也需要根据taro的配置来生成。至于怎么生成,直接搬@taro的代码(微改)就好:
1 2 3 4 5 6 7 8 9 10
| plugins.push(autoprefixer(Object.assign(defaultAutoprefixerOption, customAutoprefixerOption))); plugins.push(new autoprefixer(Object.assign(defaultAutoprefixerOption, customAutoprefixerOption)));
|
最后放在你自己的webpack.config.js文件里就好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const merge = require("lodash.merge") const { getPostcssPlugins } = require("./postcss.conf") const taroConfig = require("./config")(merge) const { designWidth, deviceRatio, h5 } = taroConfig const plugins = getPostcssPlugins({ designWidth, deviceRatio, postcssConfig: h5.module.postcss.plugins }) const postcssConfig = { loader: "postcss-loader", options: { sourceMap: true, ident: "postcss", plugins } } const entryPath = path.resolve(process.cwd(), ".temp", "app.js")
|
局限
因为目前 npm run dev:h5 命令一口气做了两件事:编译出.temp文件夹和启动webpack-dev-server。如果你的开发环境需要使用自己的dev-server,也许需要用到concurrently这个npm包来并行执行多个npm script(已建议官方拆分多一个taro compile的命令Feature Request taro-cli 能否多提供一个compile 命令, 仅生成.temp文件夹 · Issue #982 · NervJS/taro · GitHub)
最后
其实灵感来源于mpvue的同构配置。对于mpvue来说,只需要分别执行vue和mpvue的webpack就好了。对taro来说,关键就是找到编译后生成的平台相关代码(.temp),然后就是熟悉的webpack构建流程了。
同时,taro多出来的这一次编译,能够在编译阶段根据环境变量来去掉平台无关的代码,大大提高了开发者对与多端兼容的控制能力。(相比于mpvue只能在运行时做判断,无法运行时判断的就只能修改mpvue-template-compiler的代码了)
最后的最后,欢迎各位小伙伴一起来交流学习~