实战篇---微信小程序工程化探究之webpack_微信小程序
2020-09-19小程序搜奇网128°c
A+ A-
相干进修引荐:微信小程序教程
媒介
微信小程序因为其方便的运用体式格局,以极快的速率流传开来吸收了大批的运用者。市场需求急剧增添的情况下,每家互联网企业都想一尝甜头,因而掌握小程序开发这一手艺无疑是一位前端开发者不可或缺的妙技。但小程序开发当中总有一些不轻易一直让开发者诟病不已,主要表如今:
- 初期缺少轻易的npm包治理机制(现阶段确切可以运用npm包,然则操纵确切不轻易)
- 不能运用预编译言语处置惩罚款式
- 没法经由过程剧本敕令切换差别的开发环境,需手动修正对应环境所需设置(通例项目最少具有开发与生产环境)
- 没法将范例搜检东西连系到项目工程中(诸如EsLint、StyleLint的运用)
有了不少的问题以后,我入手下手思索怎样将当代的工程化手艺与小程序相连系。初期在社区中查阅材料时,很多先辈都基于gulp去做了不少实践,关于小程序这类多页运用来讲gulp的流式事情体式格局好像越发轻易。在现实的实践事后,我不太惬意运用gulp这一计划,所以我转向了对webpack的实践探究。我以为挑选webpack作为工程化的支撑,尽管它相对gulp更难完成,但在将来的发展中肯定会有特殊的结果,
实践
我们先不斟酌预编译、范例等等较为庞杂的问题,我们的第一个目的是怎样运用webpack将源代码文件夹下的文件输出到目的文件夹当中,接下来我们就一步步来竖立这个工程项目:
/* 竖立项目 */$ mkdir wxmp-base$ cd ./wxmp-base/* 竖立package.json */$ npm init/* 装置依靠包 */$ npm install webpack webpack-cli --dev复制代码
装置好依靠以后我们为这个项目竖立基础的目次构造,如图所示:

上图所展现的是一个最简朴的小程序,它只包括app
全局设置文件和一个home
页面。接下来我们不论全局或是页面,我们以文件范例划分为须要待加工的js
范例文件和不须要再加工可以直接拷贝的wxml
、wxss
、json
文件。以如许的思绪我们入手下手编写供webpack实行的设置文件,在项目根目次下竖立一个build目次寄存webpack.config.js文件。
$ mkdir build$ cd ./build$ touch webpack.config.js复制代码
/** webpack.config.js */const path = require('path');const CopyPlugin = require('copy-webpack-plugin');const ABSOLUTE_PATH = process.cwd();module.exports = { context: path.resolve(ABSOLUTE_PATH, 'src'), entry: { app: './app.js', 'pages/home/index': './pages/home/index.js' }, output: { filename: '[name].js', path: path.resolve(ABSOLUTE_PATH, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: ['@babel/plugin-transform-runtime'], }, }, } ] }, plugins: [ new CopyPlugin([ { from: '**/*.wxml', toType: 'dir', }, { from: '**/*.wxss', toType: 'dir', }, { from: '**/*.json', toType: 'dir', } ]) ] };复制代码
在编写完上述代码以后,为人人解释一下上述的代码终究会做些什么:
- 进口
entry
对象中我写了两个属性,意在将app.js
和home/index.js
作为webpack的构建进口,它会以这个文件为起始点竖立各自的依靠关联,如许当我们在进口文件中引入其他文件时,被引入的文件也能被webpack所处置惩罚。 module
中我运用了babel-loader
对js
文件举行ES6转换为ES5的处置惩罚,而且到场了对新语法的处置惩罚,如许我们就处理了在原生小程序开发中老是要重复引入regenerator-runtime
的问题。(这一步我们须要装置@babel/core
、@babel/preset-env
、@babel/plugin-transform-runtime
、@babel/runtime
、babel-loader
这几个依靠包)- 运用
copy-webpack-plugin
来处置惩罚不须要再加工的文件,这个插件可以直接将文件复制到目的目次当中。
我们相识完这些代码的现实作用以后就可以够在终端中运转webpack --config build/webpack.config.js
敕令。webpack会将源代码编译到dist
文件夹中,这个文件夹中的内容便可用在开发者东西中运转、预览、上传。
优化
完成了最基础的webpack构建战略后,我们完成了app
和home
页面的转化,但这还远远不够。我们还须要处理很多的问题:
- 页面文件增加怎样办,组件怎样处置惩罚
- 预期的预编译怎样做
- 范例怎样连系到工程中
- 环境变量怎样处置惩罚
接下来我们针对以上几点举行webpack战略的升级:
页面与组件
一入手下手我的完成要领是写一个东西函数应用glob
网络pages和components下的js
文件然后生成进口对象传递给entry
。然则在实践过程当中,我发明如许的做法有两个弊病:
- 当终端中已启动了敕令,这时候新增页面或组件都不会自动生成新的进口,也就是我们要重跑一遍敕令。
- 东西函数写死了婚配pages和components文件夹下的文件,不利于项目的延展性,如果我们须要分包或许文件夹定名须要修改时,我们就须要修改东西函数。
本着程序员应当是极端慵懒,能交给机械完成的事变毫不本身着手的信条,我入手下手研讨新的进口生成计划。终究肯定下来编写一个webpack的插件,在webpack构建的生命周期中生成进口,空话不多说上代码:
/** build/entry-extract-plugin.js */const fs = require('fs');const path = require('path');const chalk = require('chalk');const replaceExt = require('replace-ext');const { difference } = require('lodash');const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin');class EntryExtractPlugin { constructor() { this.appContext = null; this.pages = []; this.entries = []; } /** * 网络app.json文件中注册的pages和subpackages生成一个待处置惩罚数组 */ getPages() { const app = path.resolve(this.appContext, 'app.json'); const content = fs.readFileSync(app, 'utf8'); const { pages = [], subpackages = [] } = JSON.parse(content); const { length: pagesLength } = pages; if (!pagesLength) { console.log(chalk.red('ERROR in "app.json": pages字段缺失')); process.exit(); } /** 网络分包中的页面 */ const { length: subPackagesLength } = subpackages; if (subPackagesLength) { subpackages.forEach((subPackage) => { const { root, pages: subPages = [] } = subPackage; if (!root) { console.log(chalk.red('ERROR in "app.json": 分包设置中root字段缺失')); process.exit(); } const { length: subPagesLength } = subPages; if (!subPagesLength) { console.log(chalk.red(`ERROR in "app.json": 当前分包 "${root}" 中pages字段为空`)); process.exit(); } subPages.forEach((subPage) => pages.push(`${root}/${subPage}`)); }); } return pages; } /** * 以页面为起始点递回去寻觅所运用的组件 * @param {String} 当前文件的上下文途径 * @param {String} 依靠途径 * @param {Array} 包括悉数进口的数组 */ addDependencies(context, dependPath, entries) { /** 生成绝对途径 */ const isAbsolute = dependPath[0] === '/'; let absolutePath = ''; if (isAbsolute) { absolutePath = path.resolve(this.appContext, dependPath.slice(1)); } else { absolutePath = path.resolve(context, dependPath); } /** 生成以源代码目次为基准的相对途径 */ const relativePath = path.relative(this.appContext, absolutePath); /** 校验该途径是不是正当以及是不是在已有进口当中 */ const jsPath = replaceExt(absolutePath, '.js'); const isQualification = fs.existsSync(jsPath); if (!isQualification) { console.log(chalk.red(`ERROR: in "${replaceExt(relativePath, '.js')}": 当前文件缺失`)); process.exit(); } const isExistence = entries.includes((entry) => entry === absolutePath); if (!isExistence) { entries.push(relativePath); } /** 猎取json文件内容 */ const jsonPath = replaceExt(absolutePath, '.json'); const isJsonExistence = fs.existsSync(jsonPath); if (!isJsonExistence) { console.log(chalk.red(`ERROR: in "${replaceExt(relativePath, '.json')}": 当前文件缺失`)); process.exit(); } try { const content = fs.readFileSync(jsonPath, 'utf8'); const { usingComponents = {} } = JSON.parse(content); const components = Object.values(usingComponents); const { length } = components; /** 当json文件中有再援用其他组件时实行递归 */ if (length) { const absoluteDir = path.dirname(absolutePath); components.forEach((component) => { this.addDependencies(absoluteDir, component, entries); }); } } catch (e) { console.log(chalk.red(`ERROR: in "${replaceExt(relativePath, '.json')}": 当前文件内容为空或誊写不正确`)); process.exit(); } } /** * 将进口到场到webpack中 */ applyEntry(context, entryName, module) { if (Array.isArray(module)) { return new MultiEntryPlugin(context, module, entryName); } return new SingleEntryPlugin(context, module, entryName); } apply(compiler) { /** 设置源代码的上下文 */ const { context } = compiler.options; this.appContext = context; compiler.hooks.entryOption.tap('EntryExtractPlugin', () => { /** 生成进口依靠数组 */ this.pages = this.getPages(); this.pages.forEach((page) => void this.addDependencies(context, page, this.entries)); this.entries.forEach((entry) => { this.applyEntry(context, entry, `./${entry}`).apply(compiler); }); }); compiler.hooks.watchRun.tap('EntryExtractPlugin', () => { /** 校验页面进口是不是增添 */ const pages = this.getPages(); const diffPages = difference(pages, this.pages); const { length } = diffPages; if (length) { this.pages = this.pages.concat(diffPages); const entries = []; /** 经由过程新增的进口页面竖立依靠 */ diffPages.forEach((page) => void this.addDependencies(context, page, entries)); /** 去除与原有依靠的交集 */ const diffEntries = difference(entries, this.entries); diffEntries.forEach((entry) => { this.applyEntry(context, entry, `./${entry}`).apply(compiler); }); this.entries = this.entries.concat(diffEntries); } }); } }module.exports = EntryExtractPlugin;复制代码
因为webpack的plugin
相干学问不在我们这篇文章的议论领域,所以我只简朴的引见一下它是怎样参与webpack的事情流程中并生成进口的。(如果有兴致想相识这些可以私信我,偶然间的话大概会整顿一些材料出来给人人)该插件现实做了两件事:
- 经由过程compiler的entryOption钩子,我们将递归生成的进口数组一项一项的到场
entry
中。 - 经由过程compiler的watchRun钩子监听从新编译时是不是有新的页面到场,如果有就会以新到场的页面生成一个依靠数组,然后再到场
entry
中。
如今我们将这个插件运用到之前的webpack战略中,将上面的设置更改成:(记得装置chalk
replace-ext
依靠)
/** build/webpack.config.js */const EntryExtractPlugin = require('./entry-extract-plugin');module.exports = { ... entry: { app: './app.js' }, plugins: [ ... new EntryExtractPlugin() ] }复制代码
款式预编译与EsLint
款式预编译和EsLint运用实在已有很多优异的文章了,在这里我就只贴出我们的实践代码:
/** build/webpack.config.js */const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = { ... module: { rules: [ ... { enforce: 'pre', test: /\.js$/, exclude: /node_modules/, loader: 'eslint-loader', options: { cache: true, fix: true, }, }, { test: /\.less$/, use: [ { loader: MiniCssExtractPlugin.loader, }, { loader: 'css-loader', }, { loader: 'less-loader', }, ], }, ] }, plugins: [ ... new MiniCssExtractPlugin({ filename: '[name].wxss' }) ] }复制代码
我们修正完战略后就可以够将wxss
后缀名的文件更改成less
后缀名(如果你想用其他的预编译言语,可以自行修正loader),然后我们在js
文件中到场import './index.less'
语句就可以看到款式文件一般编译生成了。款式文件可以一般的生成最大的元勋就是mini-css-extract-plugin
东西包,它协助我们转换了后缀名而且生成到目的目次中。
环境切换
环境变量的切换我们运用cross-env
东西包来举行设置,我们在package.json
文件中增加两句剧本敕令:
"scripts": { "dev": "cross-env OPERATING_ENV=development webpack --config build/webpack.config.js --watch", "build": "cross-env OPERATING_ENV=production webpack --config build/webpack.config.js }复制代码
响应的我们也修正一下webpack的设置文件,将我们运用的环境也通知webpack,如许webpack会针对环境对代码举行优化处置惩罚。
/** build/webpack.config.js */const { OPERATING_ENV } = process.env;module.exports = { ... mode: OPERATING_ENV, devtool: OPERATING_ENV === 'production' ? 'source-map' : 'inline-source-map'}复制代码
虽然我们也可以经由过程敕令为webpack设置mode
,如许也可以在项目中经由过程process.env.NODE_ENV
接见环境变量,然则我照样引荐运用东西包,因为你大概会有多个环境uat
test
pre
等等。
针对JS优化
小程序对包的大小有严厉的请求,单个包的大小不能超过2M,所以我们应当对JS做进一步的优化,这有利于我们掌握包的大小。我所做的优化主要针对runtime和多个进口页面之间援用的大众部份,修正设置文件为:
/** build/webpack.config.js */module.exports = { ... optimization: { splitChunks: { cacheGroups: { commons: { chunks: 'initial', name: 'commons', minSize: 0, maxSize: 0, minChunks: 2, }, }, }, runtimeChunk: { name: 'manifest', }, }, }复制代码
webpack会将大众的部份抽离出来在dist
文件夹根目次中生成common.js
和manifest.js
文件,如许全部项目的体积就会有显著的减少,然则你会发明当我们运转敕令是开发者东西内里项目现实上是没法一般运转的,这是为何?
这主要是因为这类优化使小程序其他的js
文件丧失了对大众部份的依靠,我们对webpack设置文件做以下修正就可以够处理了:
/** build/webpack.config.js */module.exports = { ... output: { ... globalObject: 'global' }, plugins: [ new webpack.BannerPlugin({ banner: 'const commons = require("./commons");\nconst runtime = require("./runtime");', raw: true, include: 'app.js', }) ] }复制代码
相干进修引荐:js视频教程
小小解惑
很多读者大概会有迷惑,为何你不直接运用已有的框架举行开发,这些才能已有很多框架支撑了。挑选框架确切是一个不错的挑选,毕竟开箱即用为开发者带来了很多方便。然则这个挑选是有利有弊的,我也对市面上的较盛行框架做了一段时间的研讨和实践。较为初期的腾讯的wepy、美团的mpvue,后来者居上的京东的taro、Dcloud的uni-app等,这些在运用当中我以为有以下一些点不受我喜爱:
- 黑盒使我们偶然很难定位问题终究是出在本身的代码当中照样在框架的编译流程中(这让我踩了不少坑)
- 缭绕框架睁开的可以运用的资本有限,比方UI的运用基础依靠于官方团队举行配套开发,如果没有社区也极难找到须要的资本(这一点我以为uni-app的社区做得挺不错)
- 与已有的一些原生的资本没法连系,这些框架基础都是基于编译道理供应了以react或许vue为开发言语的才能,这使得原生的资本要无缝接入很难完成(如果你们公司已沉淀了一些营业组件那你会很头疼)。
- 末了一点,也是我忧郁的最主要的一点,框架的升级速率是不是能跟得上官方的迭代速率,如果滞后了已有的项目该怎样处置惩罚
以上基础是我为何要本身探究小程序工程化的来由(实在另有一点就是求知欲,嘻嘻)
写在末了
以上是我对原生小程序工程化的探究,在我地点的团队中还运用了一些相干的款式范例,在这篇文章中我没有细致的说,有兴致的话可以检察我的专栏中《团队范例之款式范例实践》一文。实在另有静态资本的治理,项目的目次的补充这些细节可以遵照团队的须要去完美补充。本文愿望对有须要做这方面实践的团队有所协助,若有看法不正确或须要革新的处所,望可以批评示知我。
以上就是实战篇---微信小程序工程化探究之webpack的细致内容,更多请关注ki4网别的相干文章!