【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析_第1頁
【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析_第2頁
【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析_第3頁
【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析_第4頁
【移動應(yīng)用開發(fā)技術(shù)】微信小程序工程化探索之webpack的示例分析_第5頁
已閱讀5頁,還剩6頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論