Heim > Artikel > WeChat-Applet > Praktisches Kapitel --- WeChat Mini Program Engineering Exploration Webpack
Verwandte Lernempfehlungen: Tutorial zum WeChat Mini-Programm
Das WeChat Mini-Programm verbreitet sich extrem schnell und zieht aufgrund seiner praktischen Nutzung eine große Anzahl von Benutzern an. Angesichts der stark steigenden Marktnachfrage möchte jedes Internetunternehmen die Vorteile nutzen. Daher ist die Beherrschung der Technologie der Entwicklung kleiner Programme zweifellos eine unverzichtbare Fähigkeit für einen Front-End-Entwickler. Allerdings gibt es bei der Entwicklung kleiner Programme immer einige Unannehmlichkeiten, die von Entwicklern schon immer kritisiert wurden. Die wichtigsten Erscheinungsformen sind:
Nachdem ich viele Fragen hatte, begann ich darüber nachzudenken, wie man moderne Ingenieurstechnologie kombinieren kann mit kleinen Programmen. Als ich in der Anfangszeit nach Informationen in der Community suchte, hatten viele Senioren viel auf der Basis von gulp geübt. Für mehrseitige Anwendungen wie kleine Programme scheint die Streaming-Arbeitsmethode von gulp bequemer zu sein. Nach der eigentlichen Übung war ich mit der Lösung, gulp zu verwenden, nicht zufrieden und wandte mich daher der praktischen Erforschung von Webpack zu. Ich denke, dass die Wahl von Webpack als technische Unterstützung, obwohl es schwieriger zu implementieren ist als Gulp, auf jeden Fall außergewöhnliche Auswirkungen auf die zukünftige Entwicklung haben wird So verwenden Sie Webpack, um die Dateien im Quellcodeordner in den Zielordner auszugeben. Als Nächstes erstellen wir dieses Projekt Schritt für Schritt:
/* 创建项目 */$ mkdir wxmp-base$ cd ./wxmp-base/* 创建package.json */$ npm init/* 安装依赖包 */$ npm install webpack webpack-cli --dev复制代码
Ich habe zwei Attribute in das Eintragsobjekt entry
geschrieben, mit der Absicht, app. js und home/index.js
dienen als Konstruktionseinstiegspunkt von Webpack. Diese Datei wird als Ausgangspunkt für die Erstellung ihrer jeweiligen Abhängigkeiten verwendet, sodass wir sie bei der Einführung in Eintragsdatei Wenn andere Dateien eingebunden sind, können die importierten Dateien auch von Webpack verarbeitet werden. app
全局配置文件和一个home
页面。接下来我们不管全局或是页面,我们以文件类型划分为需要待加工的js
类型文件和不需要再加工可以直接拷贝的wxml
、wxss
、json
文件。以这样的思路我们开始编写供webpack执行的配置文件,在项目根目录下创建一个build目录存放webpack.config.js文件。
/** 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;复制代码
/** build/webpack.config.js */const EntryExtractPlugin = require('./entry-extract-plugin');module.exports = { ... entry: { app: './app.js' }, plugins: [ ... new EntryExtractPlugin() ] }复制代码
在编写完上述代码之后,为大家解释一下上述的代码究竟会做些什么:
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
module
habe ich babel-loader
verwendet, um die js
-Datei von ES6 nach ES5 zu konvertieren, und die Verarbeitung neuer Syntax hinzugefügt Auf diese Weise lösen wir das Problem, dass bei der Entwicklung nativer kleiner Programme immer wieder regenerator-runtime
eingeführt wird. (In diesem Schritt müssen wir @babel/core
, @babel/preset-env
, @babel/plugin-transform-runtime
, @babel/runtime
, babel-loader
diese Abhängigkeiten) 🎜🎜Verwenden Sie copy-webpack-plugin
, um Dateien zu verarbeiten, die nicht erneut verarbeitet werden müssen . Dieses Plug-in kann Dateien direkt in das Zielverzeichnis kopieren. 🎜🎜Nachdem wir die eigentliche Funktion dieser Codes verstanden haben, können wir den Befehl webpack --config build/webpack.config.js
im Terminal ausführen. Webpack kompiliert den Quellcode im Ordner dist
und der Inhalt in diesem Ordner kann in den Entwicklertools ausgeführt, in der Vorschau angezeigt und hochgeladen werden. 🎜app
- und home
Konvertierung implementiert der Seite, aber das reicht nicht aus. Wir müssen noch viele Probleme lösen: 🎜🎜🎜Was tun, wenn die Auslagerungsdateien zunehmen, wie geht man mit Komponenten um? 🎜🎜Wie führt man die erwartete Vorkompilierung durch? 🎜🎜Wie integriert man Spezifikationen in das Projekt. 🎜🎜Wie geht man mit Umgebungsvariablen um 🎜🎜🎜 Als nächstes werden wir uns auf die oben genannten Punkte konzentrieren, um die Webpack-Strategie zu aktualisieren: 🎜glob
zu verwenden. Sammeln Sie js
-Dateien unter Seiten und Komponenten, generieren Sie dann das Eintragsobjekt und übergeben Sie es an entry
. In der Praxis habe ich jedoch festgestellt, dass dieser Ansatz zwei Nachteile hat: 🎜本着程序员应该是极度慵懒,能交给机器完成的事情绝不自己动手的信条,我开始研究新的入口生成方案。最终确定下来编写一个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的工作流程中并生成入口的。(如果有兴趣想了解这些可以私信我,有时间的话可能会整理一些资料出来给大家)该插件实际做了两件事:
entry
中。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应用其实已经有许多优秀的文章了,在这里我就只贴出我们的实践代码:
/** 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
等等。
小程序对包的大小有严格的要求,单个包的大小不能超过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等,这些在应用当中我认为有以下一些点不受我青睐:
Das Obige ist im Grunde der Grund Ich möchte selbst kleine Projekte erforschen (eigentlich gibt es noch einen weiteren interessanten Punkt, hehe). Stilvorgaben in meinem Team, ich habe es in diesem Artikel nicht speziell erwähnt. Wenn Sie interessiert sind, können Sie sich den Artikel „Teamnormen – Stilstandardpraxis“ in meiner Kolumne ansehen. Tatsächlich gibt es auch die Verwaltung statischer Ressourcen und das Hinzufügen von Projektverzeichnissen. Diese Details können je nach Bedarf des Teams verbessert und ergänzt werden. Ich hoffe, dass dieser Artikel für Teams hilfreich ist, die in diesem Bereich üben müssen. Wenn Sie falsche Meinungen oder Bereiche haben, die verbessert werden müssen, teilen Sie mir dies bitte in den Kommentaren mit.
Das obige ist der detaillierte Inhalt vonPraktisches Kapitel --- WeChat Mini Program Engineering Exploration Webpack. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!