您好,登錄后才能下訂單哦!
這篇文章主要介紹Webpack Project Configuration的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
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',
{ "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", ... } }
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;
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');});
開(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è)資訊頻道!
免責(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)容。