BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / java-script / #4188同步于 2018/8/16
JavaScript机器人发帖

【讨论】【心得】终极蛇皮上帝视角之微信小程序之告别“刀耕火

stevesasuke
2018/8/16镜像同步0 回复
开门见山地说,小程序在日常开发中使用原生框架来开发还是挺不方便的,比如: * 不支持 `npm` 包 * 不支持各种 `CSS` 预编译器 * 不支持配置 `Babel` 来转换一些 `JavaScript` 新特性 这样一来和日常开发前端页面的体验相比来说,简直就像在**刀耕火种**。 > 那么为了解决这些问题,我们能不能将前端开发中常用的 `webpack` 移植到小程序开发中呢? **当然可以!** https://webpack.js.org/assets/icon-square-big.svg ## 0.源码地址 https://img.shields.io/badge/webpack-%5E4.8.1-green.svg https://img.shields.io/badge/vue--loader-%5E15.0.12-green.svg * 在 [webpack-simple]((https://github.com/tuateam/tua-mp/tree/master/examples/webpack-simple/)) 中文件结构和小程序相似。 * 而在 [webpack-vue]((https://github.com/tuateam/tua-mp/tree/master/examples/webpack-vue/)) 中还增加了 `vue-loader`,因此你甚至还能利用 `.vue` 文件编写单文件组件。 https://buptsteve.github.io/blog/imgs/tua-mp/logs.vue.png ## 1.文件结构 既然用 `webpack` 来编译源代码,那么很自然的我们的文件结构首先要分为 `src/` 和 `dist/`,用开发者工具打开的应该是 `dist/` 目录。 ### 1.1.`src/` 中文件结构大概长这样: ``` . ├── app │ ├── app.js │ ├── app.json │ └── app.scss ├── assets │ └── vue-logo.png ├── comps │ └── todo │ ├── todo.js │ ├── todo.json │ ├── todo.less │ └── todo.wxml ├── pages │ └── index │ ├── index.js │ ├── index.json │ ├── index.less │ └── index.wxml ├── scripts │ ├── const │ │ ├── README.md │ │ └── index.js │ └── utils │ ├── README.md │ ├── event.js │ ├── format.js │ ├── index.js │ └── log.js ├── styles │ ├── global.styl │ ├── todomvc-app-css.css │ └── todomvc-common-base.css └── templates └── info.wxml ``` * app/: 应用入口 * assets/: 资源文件,比如图片 * comps/: 组件 * pages/: 页面 * scripts: 公用代码 * scripts/const: 常量(已配置别名 @const) * scripts/utils: 辅助函数(已配置别名 @utils) * styles/: 公用样式 * templates/: 模板 ### 1.2.`dist/` 中文件结构大概长这样: ``` . ├── app.js ├── app.js.map ├── app.json ├── app.wxss ├── assets │ └── vue-logo.png ├── chunks │ ├── runtime.js │ ├── runtime.js.map │ ├── scripts.js │ ├── scripts.js.map │ ├── vendors.js │ └── vendors.js.map ├── comps │ └── todo │ ├── todo.js │ ├── todo.js.map │ ├── todo.json │ ├── todo.wxml │ └── todo.wxss ├── pages │ └── index │ ├── index.js │ ├── index.js.map │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json └── templates └── info.wxml ``` * chunks/: 公共依赖 * runtime: [是 webapck 在运行时连接各个模块的代码](https://doc.webpack-china.org/concepts/manifest/#runtime) * vendors: 是提取的 `node_modules` 下的依赖 * scripts: 是提取的 `src/scripts/` 下的依赖 ### 1.3.整个项目文件结构大概长这样: ``` . ├── README.md ├── dist/ ├── package.json ├── project.config.json ├── src/ ├── webpack.config.babel.js └── yarn.lock ``` * src/: 源码 * dist/: 打包后代码 ## 2.webpack 基础配置 ## 2.1.entry/output 小程序场景下的配置应该是多入口,主要分为 `app`、`pages`、`comps` 这三类。 * app: 将 `src/app/` 下的文件编译成 `dist/` 根目录下的 `app.js/app.json/app.wxss` * pages: `src/pages/ -> dist/pages/` * comps: `src/comps/ -> dist/comps/` 在输出 `output` 部分有个坑:因为小程序使用的是 `global`,所以必须添加配置 `output.globalObject` 为 `global`。 > 不然... ```js thirdScriptError VM937:1 sdk uncaught third Error Cannot read property 'webpackJsonp' of undefined TypeError: Cannot read property 'webpackJsonp' of undefined at http://127.0.0.1:40247/appservice/chunks/runtime.js:34:51 at http://127.0.0.1:40247/appservice/chunks/runtime.js:38:2 at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859) at http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7573 at http://127.0.0.1:40247/appservice/app.js:3:1 at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859) at http://127.0.0.1:40247/appservice/appservice?t=1527755092895:1020:9 // runtime var a = window.webpackJsonp = window.webpackJsonp || [] ``` [详情可参阅这个 pr](https://github.com/webpack/webpack/pull/6200) > ps 在 mpvue 中似乎是通过修改 target 实现的... http://mpvue.com/build/mpvue-webpack-target/ ## 2.2.CommonChunk 在 webpack 4 中有一个 breaking change,[即使用 `SplitChunksPlugin` 替换了之前很常用的 `CommonsChunkPlugin`](https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366) 主要提取了三部分的公共代码: * runtime: [是 webapck 在运行时连接各个模块的代码](https://doc.webpack-china.org/concepts/manifest/#runtime) * vendors: 是提取的 `node_modules` 下的依赖 * scripts: 是提取的 `src/scripts/` 下的依赖 > 现在又碰到个新的问题:如何引入这些 `chunks`? 在前端项目中一般我们通过 `HtmlWebpackPlugin` 插件在 html 文件中添加 `<script>` 标签引入,然鹅小程序中并没有 html 文件... > 计将安出? 总不能每次都手动去 `dist/app.js` 中 require 这些文件吧? 这时候就要介绍另一款插件了~:`BannerPlugin`。 这个插件本来是用在文件头部添加 banner 的,但是也支持插入代码,因此利用这款插件我们就可以将这些公共依赖在 `app.js` 中统一引入一次即可。 > TODO: 现版本的小程序提供了分包加载能力,因此这里还有优化空间 ## 2.3.CopyWebpackPlugin 顾名思义,这款插件的用处就是拷贝,利用这款插件我们就可以实现: * 复制 `project.config.json` * 复制 `*.json` * 复制 `*.wxml` * 复制 `*.wxss` * 复制 `assets/` * 复制 `templates/` 在使用时有一个知识点可以减少代码量:即 `context` 选项,这样就不用写 n 个 `src/`了... ```js new CopyWebpackPlugin(copyCfgArr, { context: resolve('src'), }), ``` ## 2.4.预处理器和 CSS 的处理 这部分其实都是常规操作和一般 web 开发没啥区别,配置好对应的 loader 即可。 需要注意的点就是一定要使用 `ExtractTextWebpackPlugin` 插件来生成 `.wxss` 文件。 ```js new ExtractTextPlugin('[name].wxss') ``` ## 3.webpack + vue-loader 这部分谈谈如何利用 `vue-loader` 实现在小程序中引用单文件组件(`.vue`)。 先看看 `src/` 下的文件结构: ``` . ├── app │ ├── App.vue │ ├── app.js │ └── app.json ├── assets │ └── vue-logo.png ├── comps │ ├── filter │ │ ├── Filter.vue │ │ └── index.js │ └── todo │ ├── Todo.vue │ └── index.js ├── pages │ ├── index │ │ ├── Index.vue │ │ └── index.js │ └── todos │ ├── Todos.vue │ └── index.js ├── scripts │ ├── const │ │ ├── README.md │ │ └── index.js │ └── utils │ ├── README.md │ ├── event.js │ ├── format.js │ ├── index.js │ └── log.js ├── styles │ ├── global.styl │ ├── todomvc-app-css.css │ └── todomvc-common-base.css └── templates └── info.wxml ``` 其实已经和一般的 web 项目很相似了~ ### 3.1.vue-loader v15? 随着 webpack 升级到了 v4,官方与之配合的 `vue-loader` 也升级到了 v15。 > 现在 Vue Loader 15 使用了一个不一样的策略来推导语言块使用的 loader。 > 在 v15 中,`<style lang="less">` 会完成把它当作一个真实的 `*.less` 文件来加载。因此,为了这样处理它,你需要在你的主 webpack 配置中显式地提供一条规则。 简单来说就是咱们之前配置过的各个预处理器规则会被 `vue-loader` 自动使用。 因此我们只需要简单地添加一条规则即可读取 `.vue` 文件: ```js { test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader', options: { compiler: { // mock vue-template-compiler compile: () => ({ staticRenderFns: [], }) }, }, }, ``` > `options.compiler` 是啥? ### 3.2.options.compiler [options.compiler 覆写用来编译单文件组件中 `<template>` 块的默认编译器。](https://vue-loader.vuejs.org/zh/options.html#compiler) 在实际使用单文件组件时,我们通过 `<template lang="wxml">` 来包裹原本的 `.wxml` 文件中的内容。 因为最终要编译成 `.wxml` 文件才能被开发者工具识别,所以我们还编写了一条规则通过 `file-loader` 生成最终的 `.wxml` 文件: ```js { // 处理 <template lang="wxml">{...}</template> // 生成 .wxml 文件 test: /\.wxml$/, use: { loader: 'file-loader', options: { name: getNameByFilePathAndExt('.wxml'), }, }, }, ``` 但是因为 `vue-loader` 默认会编译 template 中的内容将其生成一个个 render 函数。但其实在小程序场景中我们并不需要这一步骤。我们只想安安静静地将这些代码通过 `file-loader` 生成 `.wxml` 文件... 幸好 `vue-loader` 还提供了 `options.compiler` 这个参数用来传递自己的编译器。所以这里其实是 mock 了一下 `vue-template-compiler`。 ### 3.3.Custom Blocks > 最后还有个问题没有解决:如何处理 `.json` 文件? 在其他的小程序框架中是这样处理的: * 在 `wepy` 中将其作为组件的 `config` 属性 ```js export default class Index extends wepy.page { //页面配置 config = { "navigationBarTitleText": "test" }; // ... } ``` * 在 `mpvue` 中是写在 `main.js` 的输出部分 ```js // main.js export default { // 这个字段走 app.json config: { // 页面前带有 ^ 符号的,会被编译成首页,其他页面可以选填,我们会自动把 webpack entry 里面的入口页面加进去 pages: ['pages/logs/main', '^pages/index/main'], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'WeChat', navigationBarTextStyle: 'black' } } } // src/pages/logs/main.js export default { config: { navigationBarTitleText: '查看启动日志' } } ``` 在 `tua-mp` 中目前采用的是自定义块的方式来实现的,即在 `.vue` 文件中新增了一个 `<config>` 块来编写配置。 ```html <config> { "navigationBarTitleText": "查看启动日志" } </config> <template lang="wxml"> ... </template> ``` 但是并没有将 `app.json` 的内容放到 `App.vue` 中,因为有时需要读取这里的页面配置。如果写到 `<config>` 中的话,就无法读取了... > 例如为了实现从分享后的页面后退返回首页这个功能,在辅助函数中就需要读取页面和 tabBar 配置,生成分享链接(实际分享地址是首页,然后从首页再导航到被分享的页面)。 因此最优解是页面配置写在 `<config>` 中,应用配置写在 `app.js` 的输出中。 > TODO: 实现 mpvue 的方式处理 `app.json` 具体的配置如下: ```js { // 处理 <config>{...}</config> 代码块 // 生成 .json 文件 resourceQuery: /blockType=config/, use: { loader: 'file-loader', options: { name: getNameByFilePathAndExt('.json'), }, }, }, ``` ## 4.总结 综上,咱们在 `webpack v4` 和 `vue-loader v15` 的帮助下,让小程序拥有了以下能力: * 加载 npm 包 * 提取 CommonChunk 减少打包体积 * babel 编译 JavaScript 代码 * 支持 less/sass/stylus 等预处理器 * 单文件组件 > 不过话又说回来了... > 原生的小程序...又不是不能用~ https://buptsteve.github.io/blog/imgs/tua-mp/又不是不能用.jpg > 注:这句话是黄章说的,Teacher Luo 没说过这话哟~ 以上 to be continued...
订阅后,新回复会通过你的通知中心匿名送达。
0 条回复
暂无回复 · 你可以订阅本帖等待新回复。