版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析
這篇文章將為大家詳細(xì)講解有關(guān)微信小程序工程化探索之webpack的示例分析,在下覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。前言微信小程序因為其便捷的使用方式,以極快的速度傳播開來吸引了大量的使用者。市場需求急劇增加的情況下,每家互聯(lián)網(wǎng)企業(yè)都想一嘗甜頭,因此掌握小程序開發(fā)這一技術(shù)無疑是一名前端開發(fā)者不可或缺的技能。但小程序開發(fā)當(dāng)中總有一些不便一直讓開發(fā)者詬病不已,主要表現(xiàn)在:初期缺乏方便的npm包管理機(jī)制(現(xiàn)階段確實可以使用npm包,但是操作確實不便)不能使用預(yù)編譯語言處理樣式無法通過腳本命令切換不同的開發(fā)環(huán)境,需手動修改對應(yīng)環(huán)境所需配置(常規(guī)項目至少具備開發(fā)與生產(chǎn)環(huán)境)無法將規(guī)范檢查工具結(jié)合到項目工程中(諸如EsLint、StyleLint的使用)有了不少的問題之后,我開始思考如何將現(xiàn)代的工程化技術(shù)與小程序相結(jié)合。初期在社區(qū)中查閱資料時,許多前輩都基于gulp去做了不少實踐,對于小程序這種多頁應(yīng)用來說gulp的流式工作方式似乎更加方便。在實際的實踐過后,我不太滿意應(yīng)用gulp這一方案,所以我轉(zhuǎn)向了對webpack的實踐探索。我認(rèn)為選擇webpack作為工程化的支持,盡管它相對gulp更難實現(xiàn),但在未來的發(fā)展中一定會有非凡的效果,實踐我們先不考慮預(yù)編譯、規(guī)范等等較為復(fù)雜的問題,我們的第一個目標(biāo)是如何應(yīng)用webpack將源代碼文件夾下的文件輸出到目標(biāo)文件夾當(dāng)中,接下來我們就一步步來創(chuàng)建這個工程項目:/*
創(chuàng)建項目
*/$
mkdir
wxmp-base$
cd
./wxmp-base/*
創(chuàng)建package.json
*/$
npm
init/*
安裝依賴包
*/$
npm
install
webpack
webpack-cli
--dev復(fù)制代碼安裝好依賴之后我們?yōu)檫@個項目創(chuàng)建基礎(chǔ)的目錄結(jié)構(gòu),如圖所示:上圖所展示的是一個最簡單的小程序,它只包含app全局配置文件和一個home頁面。接下來我們不管全局或是頁面,我們以文件類型劃分為需要待加工的js類型文件和不需要再加工可以直接拷貝的wxml、wxss、json文件。以這樣的思路我們開始編寫供webpack執(zhí)行的配置文件,在項目根目錄下創(chuàng)建一個build目錄存放webpack.config.js文件。$
mkdir
build$
cd
./build$
touch
webpack.config.js復(fù)制代碼/**
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',
}
])
]
};復(fù)制代碼在編寫完上述代碼之后,為大家解釋一下上述的代碼究竟會做些什么:入口entry對象中我寫了兩個屬性,意在將app.js和home/index.js作為webpack的構(gòu)建入口,它會以這個文件為起始點創(chuàng)建各自的依賴關(guān)系,這樣當(dāng)我們在入口文件中引入其他文件時,被引入的文件也能被webpack所處理。module中我使用了babel-loader對js文件進(jìn)行ES6轉(zhuǎn)換為ES5的處理,并且加入了對新語法的處理,這樣我們就解決了在原生小程序開發(fā)中總是要反復(fù)引入regenerator-runtime的問題。(這一步我們需要安裝@babel/core、@babel/preset-env、@babel/plugin-transform-runtime、@babel/runtime、babel-loader這幾個依賴包)使用copy-webpack-plugin來處理不需要再加工的文件,這個插件可以直接將文件復(fù)制到目標(biāo)目錄當(dāng)中。我們了解完這些代碼的實際作用之后就可以在終端中運(yùn)行webpack--configbuild/webpack.config.js命令。webpack會將源代碼編譯到dist文件夾中,這個文件夾中的內(nèi)容就可用在開發(fā)者工具中運(yùn)行、預(yù)覽、上傳。優(yōu)化完成了最基礎(chǔ)的webpack構(gòu)建策略后,我們實現(xiàn)了app和home頁面的轉(zhuǎn)化,但這還遠(yuǎn)遠(yuǎn)不夠。我們還需要解決許多的問題:頁面文件增多怎么辦,組件怎么處理預(yù)期的預(yù)編譯如何做規(guī)范如何結(jié)合到工程中環(huán)境變量怎么處理接下來我們針對以上幾點進(jìn)行webpack策略的升級:頁面與組件一開始我的實現(xiàn)方法是寫一個工具函數(shù)利用glob收集pages和components下的js文件然后生成入口對象傳遞給entry。但是在實踐過程中,我發(fā)現(xiàn)這樣的做法有兩個弊端:當(dāng)終端中已經(jīng)啟動了命令,這時候新增頁面或組件都不會自動生成新的入口,也就是我們要重跑一遍命令。工具函數(shù)寫死了匹配pages和components文件夾下的文件,不利于項目的延展性,如果我們需要分包或者文件夾命名需要改動時,我們就需要改動工具函數(shù)。本著程序員應(yīng)該是極度慵懶,能交給機(jī)器完成的事情絕不自己動手的信條,我開始研究新的入口生成方案。最終確定下來編寫一個webpack的插件,在webpack構(gòu)建的生命周期中生成入口,廢話不多說上代碼:/**
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生成一個待處理數(shù)組
*/
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":
當(dāng)前分包
"${root}"
中pages字段為空`));
process.exit();
}
subPages.forEach((subPage)
=>
pages.push(`${root}/${subPage}`));
});
}
return
pages;
}
/**
* 以頁面為起始點遞歸去尋找所使用的組件
* @param
{String}
當(dāng)前文件的上下文路徑
* @param
{String}
依賴路徑
*
@param
{Array}
包含全部入口的數(shù)組
*/
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);
}
/**
生成以源代碼目錄為基準(zhǔn)的相對路徑
*/
const
relativePath
=
path.relative(this.appContext,
absolutePath);
/**
校驗該路徑是否合法以及是否在已有入口當(dāng)中
*/
const
jsPath
=
replaceExt(absolutePath,
'.js');
const
isQualification
=
fs.existsSync(jsPath);
if
(!isQualification)
{
console.log(chalk.red(`ERROR:
in
"${replaceExt(relativePath,
'.js')}":
當(dāng)前文件缺失`));
process.exit();
}
const
isExistence
=
entries.includes((entry)
=>
entry
===
absolutePath);
if
(!isExistence)
{
entries.push(relativePath);
}
/**
獲取json文件內(nèi)容
*/
const
jsonPath
=
replaceExt(absolutePath,
'.json');
const
isJsonExistence
=
fs.existsSync(jsonPath);
if
(!isJsonExistence)
{
console.log(chalk.red(`ERROR:
in
"${replaceExt(relativePath,
'.json')}":
當(dāng)前文件缺失`));
process.exit();
}
try
{
const
content
=
fs.readFileSync(jsonPath,
'utf8');
const
{
usingComponents
=
{}
}
=
JSON.parse(content);
const
components
=
Object.values(usingComponents);
const
{
length
}
=
components;
/**
當(dāng)json文件中有再引用其他組件時執(zhí)行遞歸
*/
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')}":
當(dāng)前文件內(nèi)容為空或書寫不正確`));
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)
{
/**
設(shè)置源代碼的上下文
*/
const
{
context
}
=
compiler.options;
this.appContext
=
context;
compiler.hooks.entryOption.tap('EntryExtractPlugin',
()
=>
{
/**
生成入口依賴數(shù)組
*/
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;復(fù)制代碼由于webpack的plugin相關(guān)知識不在我們這篇文章的討論范疇,所以我只簡單的介紹一下它是如何介入webpack的工作流程中并生成入口的。(如果有興趣想了解這些可以私信我,有時間的話可能會整理一些資料出來給大家)該插件實際做了兩件事:通過compiler的entryOption鉤子,我們將遞歸生成的入口數(shù)組一項一項的加入entry中。通過compiler的watchRun鉤子監(jiān)聽重新編譯時是否有新的頁面加入,如果有就會以新加入的頁面生成一個依賴數(shù)組,然后再加入entry中?,F(xiàn)在我們將這個插件應(yīng)用到之前的webpack策略中,將上面的配置更改為:(記得安裝chalkreplace-ext依賴)/**
build/webpack.config.js
*/const
EntryExtractPlugin
=
require('./entry-extract-plugin');module.exports
=
{
...
entry:
{
app:
'./app.js'
},
plugins:
[
...
new
EntryExtractPlugin()
]
}復(fù)制代碼樣式預(yù)編譯與EsLint樣式預(yù)編譯和EsLint應(yīng)用其實已經(jīng)有許多優(yōu)秀的文章了,在這里我就只貼出我們的實踐代碼:/**
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'
})
]
}復(fù)制代碼我們修改完策略后就可以將wxss后綴名的文件更改為less后綴名(如果你想用其他的預(yù)編譯語言,可以自行修改loader),然后我們在js文件中加入import'./index.less'語句就能看到樣式文件正常編譯生成了。樣式文件能夠正常的生成最大的功臣就是mini-css-extract-plugin工具包,它幫助我們轉(zhuǎn)換了后綴名并且生成到目標(biāo)目錄中。環(huán)境切換環(huán)境變量的切換我們使用cross-env工具包來進(jìn)行配置,我們在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
}復(fù)制代碼相應(yīng)的我們也修改一下webpack的配置文件,將我們應(yīng)用的環(huán)境也告訴webpack,這樣webpack會針對環(huán)境對代碼進(jìn)行優(yōu)化處理。/**
build/webpack.config.js
*/const
{
OPERATING_ENV
}
=
process.env;module.exports
=
{
...
mode:
OPERATING_ENV,
devtool:
OPERATING_ENV
===
'production'
?
'source-map'
:
'inline-source-map'}復(fù)制代碼雖然我們也可以通過命令為webpack設(shè)置mode,這樣也可以在項目中通過process.env.NODE_ENV訪問環(huán)境變量,但是我還是推薦使用工具包,因為你可能會有多個環(huán)境uattestpre等等。針對JS優(yōu)化小程序?qū)Π拇笮∮袊?yán)格的要求,單個包的大小不能超過2M,所以我們應(yīng)該對JS做進(jìn)一步的優(yōu)化,這有利于我們控制包的大小。我所做的優(yōu)化主要針對runtime和多個入口頁面之間引用的公共部分,修改配置文件為:/**
build/webpack.config.js
*/module.exports
=
{
...
optimization:
{
splitChunks:
{
cacheGroups:
{
commons:
{
chunks:
'initial',
name:
'commons',
minSize:
0,
maxSize:
0,
minChunks:
2,
},
},
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 幼兒園水電安全培訓(xùn)
- 消防與家庭安全
- 寧夏回族自治區(qū)銀川市西夏區(qū)2024-2025學(xué)年二年級上學(xué)期期中考試數(shù)學(xué)試題
- 廣東省惠州市惠州中學(xué)2024-2025學(xué)年高二上學(xué)期11月期中考試政治試題 - 副本
- 2024-2025學(xué)年寧夏吳忠市青銅峽市寧朔中學(xué)高一(上)第一次月考生物試卷(含答案)
- 2024-2025學(xué)年初中九年級數(shù)學(xué)上冊第一次月考模擬卷含答案解析
- 中考英語語法串串燒十大狀語從句
- 優(yōu)化方案2011高考總復(fù)習(xí)地理一輪復(fù)習(xí)課件大氣的受熱過程氣壓帶和風(fēng)帶湘教版
- 固定資產(chǎn)投資專項審計
- 出納年度工作計劃范文-文書模板
- 健康中國行動知行大賽理論試題及答案
- 月老合婚真經(jīng)
- 小學(xué)生學(xué)習(xí)興趣和習(xí)慣培養(yǎng)課件
- 食品添加劑標(biāo)準(zhǔn)目錄
- 農(nóng)民專業(yè)合作社課件
- 2022年癲癇性精神病臨床路徑
- 課間安全教育主題班會課件
- 三年級心理健康教學(xué)課件 第15課 專注的力量
- 民法典 婚姻家庭編課件
- 電氣工程及其自動化專業(yè)人才需求調(diào)研報告(新)5100字
- 廣西壯族自治區(qū)北海市各縣區(qū)鄉(xiāng)鎮(zhèn)行政村村莊村名明細(xì)居民村民委員會
評論
0/150
提交評論