相關(guān)學(xué)習(xí)推薦:微信小程序教程
前言
4月份的時候我曾發(fā)布過《微信小程序工程化探索之webpack實戰(zhàn)》一文,當(dāng)時是我探索微信小程序工程化的第一階段。起初我只是為了驗證微信小程序與 webpack 是否能夠相結(jié)合(很大程度是被對于技術(shù)的好奇心驅(qū)使),對于工程化的持續(xù)交付并沒有過多的思考。但是在內(nèi)部需求的不斷沖擊下,我開始萌生以工程化手段持續(xù)簡化微信小程序開發(fā)難度的想法,最終衍生的產(chǎn)物就是這套以 Medusa 命名的微信小程序快速開發(fā)方案。
接下來我將較為詳細(xì)的分享達(dá)成這一方案的實踐過程,下文中將提到的工具我也已經(jīng)發(fā)布在 npm 上供大家下載使用。這篇文章將會覆蓋之前發(fā)表的那篇文章的全部內(nèi)容并且內(nèi)容更加豐富,所以篇幅方面也較為長請讀者們耐心閱讀。
webpack-build-miniprogram
webpack-build-miniprogram 是 Medusa 方案的基礎(chǔ)也是核心,這一工具包提供了以 webpack 構(gòu)建微信小程序的能力,并且我們可以利用 webpack 的生態(tài)持續(xù)豐富 Medusa 的功能。在講述基礎(chǔ)構(gòu)建配置之前,我們先來看看 Medusa 的目錄結(jié)構(gòu)基礎(chǔ),有了相應(yīng)的目錄約束才使得項目更加規(guī)范化。
|-- dist 編譯結(jié)果目錄 |-- src 源代碼目錄 | |-- app.js 項目入口文件 | |-- app.json 小程序配置文件 | |-- sitemap.json sitemap配置文件 | |-- assets 靜態(tài)資源存放目錄 | | |-- .gitkeep | |-- components 公共組件存放目錄 | | |-- .gitkeep | |-- dicts 公共字典存放目錄 | | |-- .gitkeep | |-- libs 第三方工具庫存放目錄(外部引入) | | |-- .gitkeep | |-- pages 頁面文件存放目錄 | | |-- index | | |-- index.js | | |-- index.json | | |-- index.less | | |-- index.wxml | |-- scripts 公共腳本存放目錄(wxs) | | |-- .gitkeep | |-- services API服務(wù)存放目錄 | | |-- .gitkeep | |-- styles | | |-- index.less 項目總通用樣式 | | |-- theme.less 項目主題樣式 | |-- templates 公共模板存放目錄 | | |-- .gitkeep | |-- utils 公共封裝函數(shù)存放目錄(自我封裝) | |-- .gitkeep |-- .env 環(huán)境變量配置文件 |-- config.yaml 編譯配置文件 |-- webpack.config.js webpack 配置擴展文件 |-- project.config.json 開發(fā)者工具配置文件 └── package.json復(fù)制代碼
基礎(chǔ)篇
webpack 這一工具現(xiàn)在已經(jīng)成為前端工程師的必備技能,復(fù)雜的工作原理讓我們對它總是有種敬畏感,所以在做微信小程序構(gòu)建策略過程中,我們先將它簡單的理解為一個“搬運工具”。它將源代碼目錄中的文件加以某些處理之后再輸出到目標(biāo)目錄中?,F(xiàn)在我們明確一下我們要搬運哪些文件,微信小程序中涉及到的主要有:
- 邏輯文件
.js
- 配置文件
.json
- 模板文件
.wxml
- 樣式文件
.wxss
.less
.scss
- 腳本文件
.wxs
- 靜態(tài)資源文件
assets/
基礎(chǔ)搬運功能
接下來我們將書寫 webpack 的公共部分配置,利用 copy-webpack-plugin 這一插件來完成大部分文件的搬運工作。
/** config/webpack.common.js */const CopyPlugin = require("copy-webpack-plugin");const config = { context: SOURCE, devtool: 'none', entry: { app: './app.js' }, output: { filename: '[name].js', path: DESTINATION }, plugins: [ new CopyPlugin([ { from: 'assets/', to: 'assets/', toType: 'dir' }, { from: '**/*.wxml', toType: 'dir' }, { from: '**/*.wxss', toType: 'dir' }, { from: '**/*.json', toType: 'dir' }, { from: '**/*.wxs', toType: 'dir' } ]) ] };復(fù)制代碼
以上簡單的配置我們就實現(xiàn)了除邏輯文件與預(yù)編譯語言文件以外的搬運工作,在配置中出現(xiàn)了 SOURCE
、 DESTINATION
兩個常量,它們分別代表的是源代碼目錄與目標(biāo)代碼目錄的絕對路徑,我們將它們抽離在單獨的字典文件中:
/** libs/dicts.js */const path = require("path"); exports.ROOT = process.cwd(); exports.SOURCE = path.resolve(this.ROOT, 'src'); exports.DESTINATION = path.resolve(this.ROOT, 'dist'); exports.NODE_ENV = process.argv.splice(2, 1)[0];復(fù)制代碼
上面搬運的文件因為不需要特殊的內(nèi)容處理,所以完全交由插件去實現(xiàn),剩余兩種類型的文件我們就需要使用到 webpack 的入口(entry)、插件(plugin) 和 loader 協(xié)同合作才能完成搬運工作。
核心入口功能
首先我們要解決如何生成入口的問題,解決了入口生成的問題才能借助 loader 去完成文件內(nèi)容的轉(zhuǎn)化。對于入口生成這一問題,我開發(fā)了另外一個插件 entry-extract-webpack-plugin 去解決。這一插件我并不打算詳細(xì)的講解實現(xiàn)的過程,我只會闡述它的核心實現(xiàn)思路(如果你有興趣進(jìn)一步了解可以下載下來直接看源碼)。
微信小程序需要建立入口網(wǎng)絡(luò)其實是有規(guī)律可循的,主包、分包都會配置在 app.json 文件中,頁面所需要的組件也會配置在 [page].json 文件中。抓住這一特點,我們可以將實現(xiàn)插件功能的核心羅列為以下幾點:
- 通過
node.js
提供的path
與fs
模塊功能,以 app.json 文件中配置的路徑為基礎(chǔ),遞歸的去尋找每個 page 所依賴的 component 路徑,最終整合在同個數(shù)組中。 - 利用 webpack 提供的 SingleEntryPlugin 和 MultiEntryPlugin 插件,在 entryOption 生命周期鉤子中將第一步收集的路徑數(shù)組注入到構(gòu)建當(dāng)中形成入口(entry)。
- 構(gòu)建監(jiān)聽的過程中如果有新的頁面添加,則通過 watchRun 生命周期將新的入口加入到之前的入口(entry)中。
以上三點是實現(xiàn)生成入口這一功能的核心思路,除了核心的實現(xiàn)思路外,我還想簡單的講解下我們?nèi)绾稳懸粋€ webpack 插件:
class EntryExtractPlugin { constructor(options) {} apply(compiler) { compiler.hooks.entryOption.tap('EntryExtractPlugin', () => { ... }); compiler.hooks.watchRun.tap('EntryExtractPlugin', () => { ... }); } }復(fù)制代碼
webpack 的插件大致是以類的形式存在,當(dāng)你使用插件時,它會自動執(zhí)行 apply 方法, 然后使用 compiler.hooks
對象上的各種生命周期屬性便可以將我們需要的處理邏輯植入到 webpack 的構(gòu)建流程當(dāng)中。
邏輯與樣式
上面解決了生成入口(entry)的問題,接下來我們在原有的基礎(chǔ)上完善一下策略。由于預(yù)編譯語言的類型較多,我為了策略的可擴展性將樣式部分的策略抽離為單獨的部件,然后在通過 webpack-merge 這一工具將它們合并起來,完整的實現(xiàn)如下:
/** config/webpack.parts.js */exports.loadCSS = ({ reg = /.css$/, include, exclude, use = [] }) => ({ module: { rules: [ { include, exclude, test: reg, use: [ { loader: require('mini-css-extract-plugin').loader }, { loader: 'css-loader' } ].concat(use) } ] } });復(fù)制代碼
/** config/webpack.common.js */const { merge } = require('webpack-merge');const MiniCssExtractPlugin = require('mini-css-extract-plugin');const parts = require('./webpack.parts.js');const config = { ... module: { rules: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ } ] }, plugins: [ ... new MiniCssExtractPlugin({ filename: '[name].wxss' }) ] };module.export = merge([ config, parts.loadCSS({ reg: /.less$/, use: ['less-loader'] }) ]);復(fù)制代碼