溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Webpack Project Configuration的示例分析

發(fā)布時(shí)間:2021-03-01 11:18:09 來(lái)源:億速云 閱讀:199 作者:小新 欄目:web開(kāi)發(fā)

這篇文章主要介紹Webpack Project Configuration的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

Webpack Project Configuration

Github Repository

本部分假設(shè)你已經(jīng)對(duì)Webpack有了大概的了解,這里我們會(huì)針對(duì)筆者自己在生產(chǎn)環(huán)境下使用的Webpack編譯腳本進(jìn)行的一個(gè)總結(jié),在介紹具體的配置方案之前筆者想先概述下該配置文件的設(shè)計(jì)的目標(biāo),或者說(shuō)是筆者認(rèn)為一個(gè)前端編譯環(huán)境應(yīng)該達(dá)成的特性,這樣以后即使Webpack被淘汰了也可以利用其他的譬如JSPM之類(lèi)的來(lái)完成類(lèi)似的工作。

  • 單一的配置文件:很多項(xiàng)目里面是把開(kāi)發(fā)環(huán)境與生產(chǎn)環(huán)境寫(xiě)了兩個(gè)配置文件,可能筆者比較懶吧,不喜歡這么做,因此筆者的第一個(gè)特性就是單一的配置文件,然后通過(guò)npm封裝不同的編譯命令傳入環(huán)境變量,然后在配置文件中根據(jù)不同的環(huán)境變量進(jìn)行動(dòng)態(tài)響應(yīng)。另外,要保證一個(gè)Boilerplate能夠在最小修改的情況下應(yīng)用到其他項(xiàng)目。

  • 多應(yīng)用入口支持:無(wú)論是單頁(yè)應(yīng)用還是多頁(yè)應(yīng)用,在Webpack中往往會(huì)把一個(gè)html文件作為一個(gè)入口。筆者在進(jìn)行項(xiàng)目開(kāi)發(fā)時(shí),往往會(huì)需要面對(duì)多個(gè)入口,即多個(gè)HTML文件,然后這個(gè)HTML文件加載不同的JS或者CSS文件。譬如登錄頁(yè)面與主界面,往往可以視作兩個(gè)不同的入口。Webpack原生提倡的配置方案是面向過(guò)程的,而筆者在這里是面向應(yīng)用方式的封裝配置。

  • 調(diào)試時(shí)熱加載:這個(gè)特性毋庸多言,不過(guò)熱加載因?yàn)樽叩檬侵虚g服務(wù)器,同時(shí)只能支持監(jiān)聽(tīng)一個(gè)項(xiàng)目,因此需要在多應(yīng)用配置的情況下加上一個(gè)參數(shù),即指定當(dāng)前調(diào)試的應(yīng)用。

  • 自動(dòng)化的Polyfill:這個(gè)是Webpack自帶的一個(gè)特性吧,不過(guò)筆者就加以整合,主要是實(shí)現(xiàn)了對(duì)于ES6、React、CSS(Flexbox)等等的自動(dòng)Polyfill。

  • 資源文件的自動(dòng)管理:這部分主要指從模板自動(dòng)生成目標(biāo)HTML文件、自動(dòng)處理圖片/字體等資源文件以及自動(dòng)提取出CSS文件等。

  • 文件分割與異步加載:可以將多個(gè)應(yīng)用中的公共文件,譬如都引用了React類(lèi)庫(kù)的話,可以將這部分文件提取出來(lái),這樣前端可以減少一定的數(shù)據(jù)傳輸。另外的話還需要支持組件的異步加載,譬如用了React Router,那需要支持組件在需要時(shí)再加載。

在發(fā)布版本中,可能需要一些特殊的配置或者插件,譬如只有在 NODE_ENV環(huán)境變量等于 production的情況下才會(huì)有邏輯配置需要添加在配置文件中,那么在Webpack的配置文件中可以使用如下定義:

var webpack    = require('webpack');
var production = process.env.NODE_ENV === 'production';
var plugins = [    
new webpack.optimize.CommonsChunkPlugin({        
name:      'main', // Move dependencies to our main file        
children:  true, // Look for common dependencies in all children,        
minChunks: 2, // How many times a dependency must come up before being extracted    }),];
if (production) {    
plugins = plugins.concat([       // Production plugins go here    ]);
}module.exports = {    
entry:   './src',    
output:  {       
 path:       'builds',        
 filename:   'bundle.js',        
 publicPath: 'builds/',    },    
 plugins: plugins,    // ...};

在發(fā)布版本中,Webpack的一些配置可以被關(guān)閉,譬如:

module.exports = {    
debug:   !production,    
devtool: production ? 
false : 'eval',

Configuration

package.jso

{  
"name": "webpack-boilerplate",  
"version": "1.0.0",  
"description": "Page-Driven Webpack Boilerplate For React-Redux Work Flow",  
"scripts": {    
"start": "node devServer.js?7.1.29",    
"storybook": "start-storybook -p 9001",    
"build:webpack": "NODE_ENV=production webpack -p --config webpack.config.js?7.1.29",    
"build": "npm run clean && npm run build:webpack",    
"build:style-check": "NODE_ENV=production CHECK=true webpack -p --config webpack.config.js?7.1.29",    
"deploy": "npm run build && ./node_modules/.bin/http-server dist",    
"clean": "rimraf dist",    
"lint": "eslint src"  
},  
"repository": {    
"type": "git",    
"url": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate"  
},  
"keywords": [    
"boilerplate",    
"live",    
"hot",    
"reload",    
"react",    
"reactjs",    
"hmr",    "edit",    "webpack",    "babel",    "react-transform",    "PostCSS(FlexBox Polyfill)"  
],  
"author": "Chevalier (http://github.com/wxyyxc1992)",  
"license": "MIT",  
"bugs": {    
"url": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate/issues"  
},  
"homepage": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate",  
"devDependencies": {    
"@kadira/storybook": "^1.17.1",    ...  },  
"dependencies": {    "boron": "^0.1.2",    ...  
}
}

webpack.config.js

var path = require('path');
var webpack = require('webpack');
//PostCSS pluginsvar autoprefixer = require('autoprefixer');
//webpack pluginsvar ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin = require('webpack/lib/DefinePlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var WebpackMd5Hash = require('webpack-md5-hash');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var NODE_ENV = process.env.NODE_ENV || "develop";//獲取命令行變量
//@region 可配置區(qū)域//定義統(tǒng)一的Application,不同的單頁(yè)面會(huì)作為不同的Application
/** 
* @function 開(kāi)發(fā)狀態(tài)下默認(rèn)會(huì)把JS文本編譯為main.bundle.js,然后使用根目錄下dev.html作為調(diào)試文件. 
* @type {*[]} 
*/
var apps = [    
{        
//required        
id: "index",
//編號(hào)        
title: "Index",//HTML文件標(biāo)題        
entry: {            
name: "index",//該應(yīng)用的入口名            
src: "./src/index.js?7.1.29",//該應(yīng)用對(duì)應(yīng)的入口文件        
},//入口文件        
indexPage: "./src/index.html",//主頁(yè)文件        
//optional        
dev: false,//判斷是否當(dāng)前正在調(diào)試,默認(rèn)為false        
compiled: true//判斷當(dāng)前是否加入編譯,默認(rèn)為true    
},    
{        
id: "helloworld",        
title: "HelloWorld",        
entry: {            
name: "helloworld",            
src: "./src/modules/helloworld/container/app.js?7.1.29"        },        
indexPage: "./src/modules/helloworld/container/helloworld.html",        
dev: false,        compiled: true    },    
{        
id: "todolist",        
title: "TodoList",        
compiled: false    },    {        
//required        
id: "counter",//編號(hào)        
title: "Counter",//HTML文件標(biāo)題        
entry: {            
name: "counter",//該應(yīng)用的入口名            
src: "./src/modules/counter/container/app.js?7.1.29",//該應(yīng)用對(duì)應(yīng)的入口文件        
},//入口文件        
indexPage: "./src/modules/counter/container/counter.html",//主頁(yè)文件        
//optional        
dev: false,//判斷是否當(dāng)前正在調(diào)試,默認(rèn)為false        
compiled: true//判斷當(dāng)前是否加入編譯,默認(rèn)為true    
},    {        
//required        
id: "form",//編號(hào)        
title: "Form",//HTML文件標(biāo)題        
entry: {            
name: "form",//該應(yīng)用的入口名            
src: "./src/modules/form/form.js?7.1.29"//該應(yīng)用對(duì)應(yīng)的入口文件        
},//入口文件        
indexPage: "./src/modules/form/form.html",//主頁(yè)文件        
//optional        
dev: true,//判斷是否當(dāng)前正在調(diào)試,默認(rèn)為false        
compiled: true//判斷當(dāng)前是否加入編譯,默認(rèn)為true    
}];//定義非直接引用依賴(lài)//定義第三方直接用Script引入而不需要打包的類(lèi)庫(kù)//使用方式即為
var $ = require("jquery")const externals = {    
jquery: "jQuery",    pageResponse: 'pageResponse'};
/*********************************************************/
/*********************************************************/
/*下面屬于靜態(tài)配置部分,修改請(qǐng)謹(jǐn)慎*/
/*********************************************************/
/*********************************************************/
//開(kāi)發(fā)時(shí)的入口考慮到熱加載,只用數(shù)組形式,即每次只會(huì)加載一個(gè)文件
var devEntry = [    'eventsource-polyfill',    'webpack-hot-middleware/client',];
//生產(chǎn)環(huán)境下考慮到方便編譯成不同的文件名,所以使用數(shù)組
var proEntry = {    "vendors": "./src/vendors.js?7.1.29",//存放所有的公共文件};
//定義HTML文件入口,默認(rèn)的調(diào)試文件為src/index.htmlvar htmlPages = [];
//遍歷定義好的app進(jìn)行構(gòu)造
apps.forEach(function (app) {    
//判斷是否加入編譯    
if (app.compiled === false) {        
//如果還未開(kāi)發(fā)好,就設(shè)置為false        
return;    
}    
//添加入入口    
proEntry[app.entry.name] = app.entry.src;   
//構(gòu)造HTML頁(yè)面    
htmlPages.push({        
filename: app.id + ".html",        
title: app.title,        
// favicon: path.join(__dirname, 'assets/images/favicon.ico'),        
template: 'underscore-template-loader!' + app.indexPage, //默認(rèn)使用underscore        
inject: false, // 使用自動(dòng)插入JS腳本,        
chunks: ["vendors", app.entry.name] //選定需要插入的chunk名    
});    
//判斷是否為當(dāng)前正在調(diào)試的    
if (app.dev === true) {        
//如果是當(dāng)前正在調(diào)試的,則加入到devEntry        
devEntry.push(app.entry.src);    
}});//@endregion 可配置區(qū)域//基本配置
var config = {    
devtool: 'source-map',    
//所有的出口文件,注意,所有的包括圖片等本機(jī)被放置到了dist目錄下,其他文件放置到static目錄下    
output: {        
path: path.join(__dirname, 'dist'),//生成目錄       
 filename: '[name].bundle.js',//文件名        
 sourceMapFilename: '[name].bundle.map'//映射名        
 // chunkFilename: '[id].[chunkhash].chunk.js',//塊文件索引    },   
  //配置插件    
  plugins: [        
  // new WebpackMd5Hash(),//計(jì)算Hash插件        
  new webpack.optimize.OccurenceOrderPlugin(),        
  new webpack.DefinePlugin({            
  'process.env': {                
  //因?yàn)槭褂脽峒虞d,所以在開(kāi)發(fā)狀態(tài)下可能傳入的環(huán)境變量為空                
  'NODE_ENV': process.env.NODE_ENV === undefined ? JSON.stringify('develop') : JSON.stringify(NODE_ENV)            },            //判斷當(dāng)前是否處于開(kāi)發(fā)狀態(tài)            __DEV__: process.env.NODE_ENV === undefined || process.env.NODE_ENV === "develop" ? JSON.stringify(true) : JSON.stringify(false)        }),        //提供者fetch Polyfill插件        new webpack.ProvidePlugin({            // 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'        }),        //提取出所有的CSS代碼        new ExtractTextPlugin('[name].css'),        //自動(dòng)分割Vendor代碼        new CommonsChunkPlugin({name: 'vendors', filename: 'vendors.bundle.js', minChunks: Infinity}),        //自動(dòng)分割Chunk代碼        new CommonsChunkPlugin({            children: true,            async: true,        })    ],    module: {        loaders: [            {                test: /\.(js|jsx)$/,                exclude: /(libs|node_modules)/,                loader:"babel",                query: {                    presets: ["es2015", "react", "stage-2"],                    plugins: [                        ["typecheck"],                        ["transform-flow-strip-types"],                        ["syntax-flow"],                        ["transform-class-properties"],                        ["transform-object-rest-spread"]                    ]                }            },            {                test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/,                loader: 'url-loader?limit=8192&name=assets/imgs/[hash].[ext]'            },// inline base64 URLs for <=8k images, direct URLs for the rest            {                test: /\.vue$/,                loader: 'vue'            }        ]    },    postcss: [        autoprefixer({browsers: ['last 10 versions', "> 1%"]})    ],//使用postcss作為默認(rèn)的CSS編譯器    resolve: {        alias: {            libs: path.resolve(__dirname, 'libs'),            nm: path.resolve(__dirname, "node_modules"),            assets: path.resolve(__dirname, "assets"),        }    }};//進(jìn)行腳本組裝config.externals = externals;//自動(dòng)創(chuàng)建HTML代碼htmlPages.forEach(function (p) {    config.plugins.push(new HtmlWebpackPlugin(p));});//為開(kāi)發(fā)狀態(tài)下添加插件if (process.env.NODE_ENV === undefined || process.env.NODE_ENV === "develop") {    //配置SourceMap    config.devtool = 'cheap-module-eval-source-map';    //設(shè)置入口為調(diào)試入口    config.entry = devEntry;    //設(shè)置公共目錄名    config.output.publicPath = '/dist/'//公共目錄名    //調(diào)試狀態(tài)下的CSS    config.module.loaders.push({        test: /\.(scss|sass|css)$/,        loader: 'style-loader!css-loader!postcss-loader!sass'    });    //添加插件    config.plugins.push(new webpack.HotModuleReplacementPlugin());    config.plugins.push(new webpack.NoErrorsPlugin());} else {    //如果是生產(chǎn)環(huán)境下    config.entry = proEntry;    //如果是生成環(huán)境下,將文件名加上hash    config.output.filename = '[name].bundle.js.[hash:8]';    //設(shè)置公共目錄名    config.output.publicPath = '/'//公共目錄名    //發(fā)布狀態(tài)下添加Loader    config.module.loaders.push({        test: /\.(scss|sass|css)$/,        loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!sass')    });    //添加代碼壓縮插件    config.plugins.push(        new webpack.optimize.UglifyJsPlugin({            compressor: {                warnings: false            }        }));    //添加MD5計(jì)算插件    //判斷是否需要進(jìn)行檢查    if (process.env.CHECK === "true") {        config.module.loaders[0].loaders.push("eslint-loader");    }}module.exports = config;

devServer.js

var path = require('path');
var express = require('express');
var webpack = require('webpack');//默認(rèn)是開(kāi)發(fā)時(shí)配置
var config = require('./webpack.config');
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {  noInfo: true,  publicPath: config.output.publicPath}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {  res.sendFile(path.join(__dirname + "/src/", "dev.html"));});//監(jiān)聽(tīng)本地端口
app.listen(3000, 'localhost', function(err) {  
if (err) {    
console.log(err);    
return;  
}  
console.log('Listening at http://localhost:3000');});

Deployment

開(kāi)始這個(gè)小節(jié)之前,可以先看下大神的一篇文章: 大公司里怎樣開(kāi)發(fā)和部署前端代碼。

對(duì)于靜態(tài)文件,第一次獲取之后,文件內(nèi)容沒(méi)改變的話,瀏覽器直接讀取緩存文件即可。那如果緩存設(shè)置過(guò)長(zhǎng),文件要更新怎么辦呢?嗯,以文件內(nèi)容的 MD5 作為文件名就是一個(gè)不錯(cuò)的解決方案。來(lái)看下用 webpack 應(yīng)該怎樣實(shí)現(xiàn)

output: {    
path: xxx,   
 publicPath: yyy,    
 filename: '[name]-[chunkhash:6].js'}

打包后的文件名加入了 hash 值

const bundler = webpack(config)bundler.run((err, stats) => {  
let assets = stats.toJson().assets  let name  for (let i = 0; 
i < assets.length; i++) {    
if (assets[i].name.startsWith('main')) {      
name = assets[i].name      
break    
}  
}  fs.stat(config.buildTemplatePath, (err, stats) => {    
if (err) {      
fs.mkdirSync(config.buildTemplatePath)    
}    writeTemplate(name)  
})
})

手動(dòng)調(diào)用 webpack 的 API,獲取打包后的文件名,通過(guò) writeTemplate更新 html 代碼。完整代碼猛戳 gitst。這樣子,我們就可以把文件的緩存設(shè)置得很長(zhǎng),而不用擔(dān)心更新問(wèn)題。

以上是“Webpack Project Configuration的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI