使用webpack管理多頁應(yīng)用技巧總結(jié)
隨著前端功能不斷豐富,前端代碼也越來越復(fù)雜難以管理。為了簡化開發(fā)的復(fù)雜度,出現(xiàn)了眾多新的處理技術(shù):模塊化、組件化、css預(yù)處理器(less,scss)等,它們提高了我們開發(fā)效率,但眾多模塊文件的處理打包還是會(huì)非常繁瑣的。
Webpack是一個(gè)nodejs工具,它的工作方式是:把你的項(xiàng)目當(dāng)做一個(gè)整體,通過一個(gè)給定的主文件(如:index.js),Webpack將從這個(gè)文件開始找到你的項(xiàng)目的所有依賴文件,使用loaders處理它們,***打包為一個(gè)瀏覽器可識(shí)別的JavaScript文件。
- webpack {entry file/入口文件}
- {destination for bundled file/存放bundle.js的地方}
編寫它的默認(rèn)配置文件webpack.config.js,加入簡單配置就可以簡化上面的命令為webpack(非全局安裝需使用node_modules/.bin/webpack):
- module.exports = {
- entry: __dirname + "/development/main.js",//已多次提及的唯一入口文件
- output: {
- path: __dirname + "/build",//打包后的文件存放的地方
- filename: "bundle.js"//打包后輸出文件的文件名
- }
接上一步,繼續(xù)修改該node的配置文件package.json,加入如下配置就可以使用更簡單的命令npm start:
- "scripts": {
- "start": "webpack"
- ......
- },
這就是webpack的簡單用法了,下面開始詳細(xì)介紹webpack的配置文件。
一、.webpack的配置文件其實(shí)就是一個(gè)node的module,用commonJS風(fēng)格編寫。默認(rèn)名為webpack.config.js,要自定義配置文件名可以在webpack命令中指定(顯然不能寫到配置文件中):
webpack --config ./webpackConfig/dev.config.js
默認(rèn)會(huì)以當(dāng)前目錄為基本目錄(可以通過--content-base來更換基本目錄),webpack-dev-server生成的包并沒有放在你的真實(shí)目錄中,而是放在了內(nèi)存中??梢粤硗忾_一個(gè)cmd,使用監(jiān)控命令來輸出到磁盤:
webpack --watch
二、entry段
可以是字符串(單入口),可以是數(shù)組(多入口),但為了后續(xù)發(fā)展,建議【使用object】。entry.value是入口文件或者內(nèi)置模塊名,entry.key就是output.filename中的[name]變量的值。
- entry值是字符串或者數(shù)組時(shí)時(shí),output中沒有變量[name],entry值是對象時(shí),output中才有變量[name]。
- entry值是數(shù)組,表示把數(shù)組中的所有js文件內(nèi)容都打包到一個(gè)js文件內(nèi)作為入口js文件。
- entry的值是對象,表示這是一個(gè)多頁面應(yīng)用,對象里每一個(gè)屬性代表一個(gè)入口js文件配置。屬性的值還可以是數(shù)組,表示數(shù)組中的文件都會(huì)被打包到同一個(gè)文件中,一般用來打包合并第三方插件代碼到同一個(gè)js文件,減少網(wǎng)絡(luò)請求次數(shù)。
三、output段
相當(dāng)于【一套規(guī)則】,所有的入口都必須使用這一套規(guī)則,不能針對某一個(gè)特定的入口來制定output規(guī)則。
- output中的常用參數(shù)包括:path/publicPath/filename/chunkFilename。
- path參數(shù)(只是給webpack用的)表示生成文件的根目錄,需要傳入一個(gè)【文件系統(tǒng)絕對路徑】。path參數(shù)和后面的filename參數(shù)共同組成入口文件的完整路徑。
- publicPath(是給webpack-dev-server用的)參數(shù)表示的是一個(gè)【URL路徑】(指向生成文件的根目錄),用于生成css/js/圖片/字體文件等資源的起始路徑,會(huì)自動(dòng)加到資源文件的url前面。
- path參數(shù)其實(shí)是針對本地文件系統(tǒng)的,而publicPath則針對的是瀏覽器;因此,publicPath既可以是一個(gè)相對路徑(相對當(dāng)前代碼所在文件路徑),如示例中的'../../../../build/',也可以是一個(gè)絕對路徑如http://www.xxxxx.com/。一般來說,我還是更推薦相對路徑的寫法,這樣的話整體遷移起來非常方便。那什么時(shí)候用絕對路徑呢?其實(shí)也很簡單,當(dāng)你的html文件跟其它資源放在不同的域名下的時(shí)候,就應(yīng)該用絕對路徑了,這種情況非常多見于后端渲染模板的場景。
- filename參數(shù)是生成出來的入口文件的【命名規(guī)則】:[name]指代entry配置的key,[hash]與版本有關(guān),每次編譯都不一樣,但在同一次編譯過程中生成的文件它都是一樣的,[chunkhash]對每個(gè)文件生成hash,與文件內(nèi)容有關(guān),而與版本無關(guān)。
- chunkFilename參數(shù)也是用來定義生成文件的命名方式,針對除入口文件外的chunk命名。
- library參數(shù)中的變量[name]就是webpack.DllPlugin中name參數(shù)的變量[name]
四、用CommonsChunkPlugin【智能】判斷提取并打包、出現(xiàn)在入口js文件中的公共代碼
- var commonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin({
- name: 'commons', // 放共有代碼的chunck的唯一標(biāo)識(shí)符(可以在后面filename中用[name]引用)
- filename: '[name].bundle.js', // 放共有代碼的文件名的模板([name]=CommonsChunkPlugin.name),它的省略值就是[name].js
- minChunks: 4, // 設(shè)定要有4個(gè)chunk(即4個(gè)入口)都有的代碼才會(huì)被納入公共代碼,默認(rèn)在所有入口文件中都出現(xiàn)的代碼才會(huì)提取。
- chunks:[],//表示需要在哪些chunk里尋找公共代碼進(jìn)行打包。則默認(rèn)提取范圍為所有的chunk。
- });//最終生成文件的url是ouput.path + CommonsChunkPlugin.filename
它的作用在于:在以入口文件為單位打包js的前提下,再進(jìn)一步去提取入口文件間的共有代碼:
1.如果name的值是不存在的chunk,則filename的值不能與現(xiàn)有的入口文件打包后的名字重復(fù):
- 如果只給了name、filename,就是在<所有入口js>中提取<全部都有>的代碼單獨(dú)打包
- 如果給minChunks:2,針對<全部都有>,表示限定了在<所有入口js>中找,只有同時(shí)在任意2個(gè)入口文件中都出現(xiàn)的代碼才會(huì)被提取單獨(dú)打包
- 如果給chunks:['a','b'],針對<所有入口js>,表示只從a、b這兩個(gè)入口文件中提取<全部都有>的代碼單獨(dú)打包
2.如果name的值是現(xiàn)有chunk(entry.key),就會(huì)提取共有代碼放到同名的入口文件中(還可以通過filename改變對它打包后的文件名,通過minChunks規(guī)定在多少個(gè)入口文件中都出現(xiàn)了的代碼才算共有代碼,通過chunks需要提取共有代碼的入口文件范圍)。
五、兼容老式的jQuery插件的方式
解決使用老的jquery插件時(shí)報(bào)$/jQuery未定義錯(cuò)誤。原因是我們在require jquery的時(shí)候,實(shí)際上并不會(huì)把jQuery對象設(shè)置為全局變量。jquery插件們找不到j(luò)Query對象了,因?yàn)樵谒鼈兏髯缘纳舷挛沫h(huán)境里,既沒有局部變量jQuery(因?yàn)闆]有適配AMD/CMD,所以代碼內(nèi)部就沒有寫相應(yīng)的require語句引入依賴),也沒有全局變量jQuery。(切記【三種方法不要混用】):
方法一、ProvidePlugin + expose-loader【用ProvidePlugin向插件中引入局部變量jQuery、用expose-loader引入全局變量jQuery】。
- var providePlugin = new webpack.ProvidePlugin({
- $: 'jquery',
- jQuery: 'jquery',
- 'window.jQuery': 'jquery',
- 'window.$': 'jquery',
- });
ProvidePlugin的機(jī)制是:【當(dāng)webpack加載到某個(gè)js模塊里,出現(xiàn)了未定義且名稱符合(字符串完全匹配)配置中key的變量時(shí)】,會(huì)自動(dòng)require配置中value所指定的js模塊。使用ProvidePlugin還有個(gè)好處,就是,你自己寫的代碼里,再!也!不!用require jquery啦!。
expose-loader【將指定js模塊export的變量聲明為全局變量】。如果你所有的jQuery插件都是用webpack來加載的話,的確用ProvidePlugin就足夠了;但總有那么些需求是只能用<script>來加載的:
- {
- test: require.resolve('jquery'), // 此loader配置項(xiàng)的目標(biāo)是NPM中的jquery的資源路徑
- loader: 'expose?$!expose?jQuery', // 先把jQuery對象聲明成為全局變量`jQuery`,再通過管道進(jìn)一步又聲明成為全局變量`$`
- },
方法二、使用externals配置項(xiàng),用來【將某個(gè)全局變量“偽裝”成某個(gè)js模塊的exports】,當(dāng)某個(gè)js模塊顯式地調(diào)用var $ = require('jquery')的時(shí)候,就會(huì)把window.jQuery返回給它,但是要先在頁面中寫<script>標(biāo)簽手動(dòng)加載 jquery.min.js(意味著如果某個(gè)庫沒有提供生產(chǎn)環(huán)境的文件XXX.min.js就不能用這個(gè)方法):
- externals: {
- 'jquery': 'window.jQuery',
- }
方法三、imports-loader,相當(dāng)于【手動(dòng)版的ProvidePlugin】,不建議使用
- {
- test: require.resolve("some-module"),
- loader: "imports?$=jquery&jQuery=jquery", // 相當(dāng)于`var $ = require("jquery");var jQuery = require("jquery");`
- }
六、其它資源的打包處理方式
loader【擴(kuò)展了require()方法的能力】,使它可以以模塊的形式導(dǎo)入非js文件,處理***導(dǎo)出的都是javascrit。loader有兩種使用方式,一種是寫在require的參數(shù)里: require("!style!css!./style.css");還有一種是些到配置文件中,通過擴(kuò)展名自動(dòng)綁定loader。
1.對css的處理方式
less-loader模塊對less文件進(jìn)行編譯,但并不會(huì)針對url()語法做特別的轉(zhuǎn)換。如果想把url()語句里涉及到的文件(比如圖片、字體文件等)也一并用webpack打包的話,就必須利用管道交給css-loader做進(jìn)一步的處理。在css-loader會(huì)把它轉(zhuǎn)成require()語句,從而觸發(fā)在webpack配置文件里定義好可以處理這類資源的其它loader(比如url-loader,file-loader)。一般我在url()語句里都會(huì)以相對路徑的方式(相對于此語句所在的less/css文件)來指定資源路徑。
方法一、可以使用style-loader直接把css代碼段跟js打包在一起,并自動(dòng)用<style>標(biāo)簽插入到頁面。
方法二、也可以用ExtractTextPlugin生成并加載CSS文件(結(jié)果是每個(gè)入口js都只對應(yīng)一個(gè)css文件)的形式。extractTextPlugin會(huì)對每一個(gè)entry執(zhí)行操作:查找entry中所有通過require()語句導(dǎo)入的css代碼,把它們打包到特定路徑(由插件的唯一參數(shù)指定)的一個(gè)css文件中:
- var precss = require('precss');
- var autoprefixer = require('autoprefixer');
- var ExtractTextPlugin = require('extract-text-webpack-plugin');
- ......
- module.exports = {
- ......
- module: {
- loaders: [
- {
- test: /\.css$/,
- exclude: /node_modules|bootstrap/,
- //關(guān)閉autoprefixer以避免你的廢棄CSS代碼被css-loader刪除了
- //***步:loader里加入ExtractTextPlugin.extract(),這樣才會(huì)提取css到獨(dú)立的文件
- loader: ExtractTextPlugin.extract('css-loader?minimize&-autoprefixer!postcss-loader'),
- }
- ]
- },
- //PostCSS 是ExtractTextPlugin的插件,主要功能只有兩個(gè):***個(gè)就是前面提到的把 CSS 解析成 JavaScript 可以操作的抽象語法樹結(jié)構(gòu)(AST),第二個(gè)就是調(diào)用插件來處理 AST 并得到結(jié)果。
- postcss: function () {
- return [precss, autoprefixer({
- remove: false,
- browsers: ['ie >= 8', '> 1% in CN'],
- })];
- },
- ......
- plugins: [
- //第二步:指定打包c(diǎn)ss文件的文件名和路徑(相對于output.path)
- new ExtractTextPlugin('[name]/styles.css'), // [name]=entry.key,表示每一個(gè)入口js文件可以對應(yīng)打包一個(gè)css文件,不用這個(gè)[name]變量就會(huì)把所有入口文件中的require("XXX.css")語句引入的css文件內(nèi)容都打包到同一個(gè)文件中。
- ]
- }
2.對圖片和字體文件的處理方式
圖片可以直接在js文件中通過require導(dǎo)入,也可以在css中通過url()引入,所以對應(yīng)有兩種打包方式:
方法一、require('!url-loader?limit=8192&name=static/images/[hash].[ext]!./imgs/login-bg.jpg')//只對單個(gè)圖片文件,用于在js文件中導(dǎo)入圖片
方法二、loader: 'url?limit=8192&name=./static/img/[hash].[ext]'//可以匹配文件后綴,從而批量處理,用于在css文件中的url()語句處理
表示圖片小于8k就轉(zhuǎn)成url(dataURL)直接替換原url()語句,否則打包到output.publicPath + /static/img/[hash].[ext](只是簡單的字符串拼接,沒有路徑解析操作),然后用這個(gè)路徑替換掉原url()中的路徑。
file-loader的主要功能是:把源文件遷移到指定的目錄(可以簡單理解為從源文件目錄遷移到build目錄),并返回新文件的路徑(只是簡單的字符串拼接,沒有路徑解析操作),與url-loader的使用方式相同(沒有l(wèi)imit參數(shù))
七、用DllPlugin&DllReferencePlugin把第三方庫預(yù)打包成dll
但與CommonsChunkPlugin不一樣的是,它不僅僅是把公用代碼提取出來,還進(jìn)一步把公用代碼和它的使用者(業(yè)務(wù)代碼)從編譯這一步就分離出來,以做到分別編譯公用代碼和業(yè)務(wù)代碼。因?yàn)闃I(yè)務(wù)代碼常改,而公用代碼不常改,開發(fā)過程中就可以做到只編譯一次公用代碼,在以后的修改重編譯過程中就會(huì)把公用代碼排除在外。
1.編寫配置文件ddl.config.js:
- const webpack = require('webpack');
- module.exports = {
- output: {
- path: 'build',
- filename: '[name].js',
- library: '[name]',//與webpack.DllPlugin.name保持一樣
- },
- entry: {
- "lib": ['react','react-dom','react-router',]//需要打包成dll的公共模塊
- },
- plugins: [
- new webpack.DllPlugin({
- path: 'manifest.json',
- name: '[name]',//與output.library保持一樣
- context: __dirname,//與DllReferencePlugin.context保持一樣,建議統(tǒng)一設(shè)置為項(xiàng)目根目錄
- }),
- ],
- };
2.編譯一次公共代碼:
webpack --progress --colors --config ./ddl.config.js
,結(jié)果會(huì)輸出兩個(gè)文件一個(gè)是打包好的 lib.js,一個(gè)就是 manifest.json
3.在打包業(yè)務(wù)代碼的配置文件webpack.config.js中引用:
- plugins: [
- new webpack.DllReferencePlugin({
- context: __dirname,//DllPlugin.context保持一樣,建議統(tǒng)一設(shè)置為項(xiàng)目根目錄
- manifest: require('./manifest.json'),
- }),
- ]
4.以后業(yè)務(wù)代碼修改只需要運(yùn)行:
webpack --progress --colors --config ./webpack.config.js
,而不用再執(zhí)行編譯公共代碼的命令了。
八、自動(dòng)生成html頁面
每一個(gè)html-webpack-plugin的對象實(shí)例都只針對/生成一個(gè)頁面(會(huì)自動(dòng)向模板文件里插入依賴的js、css標(biāo)簽,***生成html文件),因此,我們做多頁應(yīng)用的話,就要配置多個(gè)html-webpack-plugin的對象實(shí)例:
- new HtmlWebpackPlugin({
- filename: `${page}/page.html`,//生成html頁面的命名,最終位置:output.path + HtmlWebpackPlugin.filename
- template: path.resolve(dirVars.pagesDir, `./${page}/html.js`),//生成html需要引入的模板路徑
- chunks: [page, 'commons/commons'],//指定生成的html文件中需要引入的、打包好的入口js文件,
- //如果使用了CommonsChunkPlugin,則這里必須填寫存放公共js代碼的chunk的name
- hash: true, // 為靜態(tài)資源生成hash值
- minify: true,
- xhtml: true,
- });
另外,webpack默認(rèn)支持的ejs模板,還需要在webpack配置文件中配置好相應(yīng)的loader。
九、用resolve為require做請求重定向
可以提高打包效率節(jié)省時(shí)間。這里的請求是對模塊的依賴,也就是一個(gè) require 語句,而不是一個(gè) HTTP 請求:
- resolve: {
- alias: {
- myModule: "app/my.min.js"//require('myModule')就相當(dāng)于require('app/my.min.js')
- }
- }
參考資料:https://segmentfault.com/a/1190000006843916