您好,登錄后才能下訂單哦!
這篇文章主要介紹Webpack Project Configuration的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
Github Repository
本部分假設你已經對Webpack有了大概的了解,這里我們會針對筆者自己在生產環境下使用的Webpack編譯腳本進行的一個總結,在介紹具體的配置方案之前筆者想先概述下該配置文件的設計的目標,或者說是筆者認為一個前端編譯環境應該達成的特性,這樣以后即使Webpack被淘汰了也可以利用其他的譬如JSPM之類的來完成類似的工作。
單一的配置文件:很多項目里面是把開發環境與生產環境寫了兩個配置文件,可能筆者比較懶吧,不喜歡這么做,因此筆者的第一個特性就是單一的配置文件,然后通過npm封裝不同的編譯命令傳入環境變量,然后在配置文件中根據不同的環境變量進行動態響應。另外,要保證一個Boilerplate能夠在最小修改的情況下應用到其他項目。
多應用入口支持:無論是單頁應用還是多頁應用,在Webpack中往往會把一個html文件作為一個入口。筆者在進行項目開發時,往往會需要面對多個入口,即多個HTML文件,然后這個HTML文件加載不同的JS或者CSS文件。譬如登錄頁面與主界面,往往可以視作兩個不同的入口。Webpack原生提倡的配置方案是面向過程的,而筆者在這里是面向應用方式的封裝配置。
調試時熱加載:這個特性毋庸多言,不過熱加載因為走得是中間服務器,同時只能支持監聽一個項目,因此需要在多應用配置的情況下加上一個參數,即指定當前調試的應用。
自動化的Polyfill:這個是Webpack自帶的一個特性吧,不過筆者就加以整合,主要是實現了對于ES6、React、CSS(Flexbox)等等的自動Polyfill。
資源文件的自動管理:這部分主要指從模板自動生成目標HTML文件、自動處理圖片/字體等資源文件以及自動提取出CSS文件等。
文件分割與異步加載:可以將多個應用中的公共文件,譬如都引用了React類庫的話,可以將這部分文件提取出來,這樣前端可以減少一定的數據傳輸。另外的話還需要支持組件的異步加載,譬如用了React Router,那需要支持組件在需要時再加載。
在發布版本中,可能需要一些特殊的配置或者插件,譬如只有在 NODE_ENV環境變量等于 production的情況下才會有邏輯配置需要添加在配置文件中,那么在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, // ...};
在發布版本中,Webpack的一些配置可以被關閉,譬如:
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 可配置區域//定義統一的Application,不同的單頁面會作為不同的Application /** * @function 開發狀態下默認會把JS文本編譯為main.bundle.js,然后使用根目錄下dev.html作為調試文件. * @type {*[]} */ var apps = [ { //required id: "index", //編號 title: "Index",//HTML文件標題 entry: { name: "index",//該應用的入口名 src: "./src/index.js?7.1.29",//該應用對應的入口文件 },//入口文件 indexPage: "./src/index.html",//主頁文件 //optional dev: false,//判斷是否當前正在調試,默認為false compiled: true//判斷當前是否加入編譯,默認為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",//編號 title: "Counter",//HTML文件標題 entry: { name: "counter",//該應用的入口名 src: "./src/modules/counter/container/app.js?7.1.29",//該應用對應的入口文件 },//入口文件 indexPage: "./src/modules/counter/container/counter.html",//主頁文件 //optional dev: false,//判斷是否當前正在調試,默認為false compiled: true//判斷當前是否加入編譯,默認為true }, { //required id: "form",//編號 title: "Form",//HTML文件標題 entry: { name: "form",//該應用的入口名 src: "./src/modules/form/form.js?7.1.29"//該應用對應的入口文件 },//入口文件 indexPage: "./src/modules/form/form.html",//主頁文件 //optional dev: true,//判斷是否當前正在調試,默認為false compiled: true//判斷當前是否加入編譯,默認為true }];//定義非直接引用依賴//定義第三方直接用Script引入而不需要打包的類庫//使用方式即為 var $ = require("jquery")const externals = { jquery: "jQuery", pageResponse: 'pageResponse'}; /*********************************************************/ /*********************************************************/ /*下面屬于靜態配置部分,修改請謹慎*/ /*********************************************************/ /*********************************************************/ //開發時的入口考慮到熱加載,只用數組形式,即每次只會加載一個文件 var devEntry = [ 'eventsource-polyfill', 'webpack-hot-middleware/client',]; //生產環境下考慮到方便編譯成不同的文件名,所以使用數組 var proEntry = { "vendors": "./src/vendors.js?7.1.29",//存放所有的公共文件}; //定義HTML文件入口,默認的調試文件為src/index.htmlvar htmlPages = []; //遍歷定義好的app進行構造 apps.forEach(function (app) { //判斷是否加入編譯 if (app.compiled === false) { //如果還未開發好,就設置為false return; } //添加入入口 proEntry[app.entry.name] = app.entry.src; //構造HTML頁面 htmlPages.push({ filename: app.id + ".html", title: app.title, // favicon: path.join(__dirname, 'assets/images/favicon.ico'), template: 'underscore-template-loader!' + app.indexPage, //默認使用underscore inject: false, // 使用自動插入JS腳本, chunks: ["vendors", app.entry.name] //選定需要插入的chunk名 }); //判斷是否為當前正在調試的 if (app.dev === true) { //如果是當前正在調試的,則加入到devEntry devEntry.push(app.entry.src); }});//@endregion 可配置區域//基本配置 var config = { devtool: 'source-map', //所有的出口文件,注意,所有的包括圖片等本機被放置到了dist目錄下,其他文件放置到static目錄下 output: { path: path.join(__dirname, 'dist'),//生成目錄 filename: '[name].bundle.js',//文件名 sourceMapFilename: '[name].bundle.map'//映射名 // chunkFilename: '[id].[chunkhash].chunk.js',//塊文件索引 }, //配置插件 plugins: [ // new WebpackMd5Hash(),//計算Hash插件 new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin({ 'process.env': { //因為使用熱加載,所以在開發狀態下可能傳入的環境變量為空 'NODE_ENV': process.env.NODE_ENV === undefined ? JSON.stringify('develop') : JSON.stringify(NODE_ENV) }, //判斷當前是否處于開發狀態 __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'), //自動分割Vendor代碼 new CommonsChunkPlugin({name: 'vendors', filename: 'vendors.bundle.js', minChunks: Infinity}), //自動分割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作為默認的CSS編譯器 resolve: { alias: { libs: path.resolve(__dirname, 'libs'), nm: path.resolve(__dirname, "node_modules"), assets: path.resolve(__dirname, "assets"), } }};//進行腳本組裝config.externals = externals;//自動創建HTML代碼htmlPages.forEach(function (p) { config.plugins.push(new HtmlWebpackPlugin(p));});//為開發狀態下添加插件if (process.env.NODE_ENV === undefined || process.env.NODE_ENV === "develop") { //配置SourceMap config.devtool = 'cheap-module-eval-source-map'; //設置入口為調試入口 config.entry = devEntry; //設置公共目錄名 config.output.publicPath = '/dist/'//公共目錄名 //調試狀態下的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 { //如果是生產環境下 config.entry = proEntry; //如果是生成環境下,將文件名加上hash config.output.filename = '[name].bundle.js.[hash:8]'; //設置公共目錄名 config.output.publicPath = '/'//公共目錄名 //發布狀態下添加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計算插件 //判斷是否需要進行檢查 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');//默認是開發時配置 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"));});//監聽本地端口 app.listen(3000, 'localhost', function(err) { if (err) { console.log(err); return; } console.log('Listening at http://localhost:3000');});
開始這個小節之前,可以先看下大神的一篇文章: 大公司里怎樣開發和部署前端代碼。
對于靜態文件,第一次獲取之后,文件內容沒改變的話,瀏覽器直接讀取緩存文件即可。那如果緩存設置過長,文件要更新怎么辦呢?嗯,以文件內容的 MD5 作為文件名就是一個不錯的解決方案。來看下用 webpack 應該怎樣實現
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) }) })
手動調用 webpack 的 API,獲取打包后的文件名,通過 writeTemplate更新 html 代碼。完整代碼猛戳 gitst。這樣子,我們就可以把文件的緩存設置得很長,而不用擔心更新問題。
以上是“Webpack Project Configuration的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。