您好,登錄后才能下訂單哦!
在知乎上我們常常會(huì)看到有同學(xué)發(fā)問(wèn):BAT 等大型網(wǎng)站的前端工程是如何組織管理的?這的確是一個(gè)可以發(fā)散的很廣的 Q&A,我想如果要我回答這個(gè)問(wèn)題,不如先從 Webpack 配置說(shuō)起。
時(shí)至今日,Webpack 已經(jīng)成為前端工程必備的基礎(chǔ)工具之一,不僅被廣泛用于前端工程發(fā)布前的打包,還在開(kāi)發(fā)中擔(dān)當(dāng)本地前端資源服務(wù)器(assets server)、模塊熱更新(hot module replacement)、API Proxy 等角色,結(jié)合 ESLint 等代碼檢查工具,還可以實(shí)現(xiàn)在對(duì)源代碼的嚴(yán)格校驗(yàn)檢查。
正如上文中提到的,前端從開(kāi)發(fā)到部署前都離不開(kāi) Webpack 的參與,而 Webpack 的默認(rèn)配置文件只有一個(gè),即 webpack.config.js,那么問(wèn)題來(lái)了,開(kāi)發(fā)期和部署前應(yīng)該使用同一份 Webpack 配置嗎?答案肯定是否定的,既然 webpack.config.js 是一個(gè) JS 文件,我們當(dāng)然可以在文件里寫(xiě) JavaScript 業(yè)務(wù)邏輯,通過(guò)讀取環(huán)境變量 NODE_ENV 來(lái)判斷當(dāng)前是在開(kāi)發(fā)(dev)時(shí)還是最終的生產(chǎn)環(huán)境(production),然而很多同學(xué)習(xí)慣把這兩者的配置都混寫(xiě)在根目錄下的 webpack.config.js,通過(guò)很多零散的 if...else 來(lái)“臨時(shí)”決定某一個(gè) plugin 或者某一個(gè) loader 的配置項(xiàng),隨著 loaders 和 plugins 的不斷增加,久而久之 webpack.config.js 變得原來(lái)越隆長(zhǎng),代碼的可讀性和可維護(hù)性也大大下降。
我想通過(guò)本文來(lái)介紹一種用 3 個(gè) JS 文件來(lái)配置 Webpack 的方法,這里借鑒了很多開(kāi)源項(xiàng)目的配置,同時(shí)也結(jié)合了我們自己在開(kāi)發(fā)中碰到的種種問(wèn)題解決方案。
本文中提及的配置基于 Webpack 2 或以上,建議使用 3.0 及以上版本
開(kāi)發(fā)環(huán)境與生產(chǎn)環(huán)境的區(qū)別
開(kāi)發(fā)環(huán)境
·NODE_ENV 為 development
·啟用模塊熱更新(hot module replacement)
·額外的 webpack-dev-server 配置項(xiàng),API Proxy 配置項(xiàng)
·輸出 Sourcemap
生產(chǎn)環(huán)境
·NODE_ENV 為 production
· 將 React、jQuery 等常用庫(kù)設(shè)置為 external,直接采用 CDN 線上的版本
· 樣式源文件(如 css、less、scss 等)需要通過(guò) ExtractTextPlugin 獨(dú)立抽取成 css 文件
· 啟用 post-css
· 啟用 optimize-minimize(如 uglify 等)
·中大型的商業(yè)網(wǎng)站生產(chǎn)環(huán)境下,是絕對(duì)不能有 console.log() 的,所以要為 babel 配置Remove console transform
這里需要說(shuō)明的是因?yàn)殚_(kāi)發(fā)環(huán)境下啟用了 hot module replacement,為了讓樣式源文件的修改也同樣能被熱替換,不能使用 ExtractTextPlugin,而轉(zhuǎn)為隨 JS Bundle 一起輸出。
你需要三份配置文件
1. webpack.base.config.js
在 base 文件里,你需要將開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境中通用的配置集中放在這里:
const CleanWebpackPlugin = require('clean-webpack-plugin'); const path = require('path'); const webpack = require('webpack'); // 配置常量 // 源代碼的根目錄(本地物理文件路徑) const SRC_PATH = path.resolve('./src'); // 打包后的資源根目錄(本地物理文件路徑) const ASSETS_BUILD_PATH = path.resolve('./build'); // 資源根目錄(可以是 CDN 上的絕對(duì)路徑,或相對(duì)路徑) const ASSETS_PUBLIC_PATH = '/assets/'; module.exports = { context: SRC_PATH, // 設(shè)置源代碼的默認(rèn)根路徑 resolve: { extensions: ['.js', '.jsx'] // 同時(shí)支持 js 和 jsx }, entry: { // 注意 entry 中的路徑都是相對(duì)于 SRC_PATH 的路徑 vendor: './vendor', a: ['./entry-a'], b: ['./entry-b'], c: ['./entry-c'] }, output: { path: ASSETS_BUILD_PATH, publicPath: ASSETS_PUBLIC_PATH, filename: './[name].js' }, module: { rules: [ { enforce: 'pre', // ESLint 優(yōu)先級(jí)高于其他 JS 相關(guān)的 loader test: /\.jsx?$/, exclude: /node_modules/, loader: 'eslint-loader' }, { test: /\.jsx?$/, exclude: /node_modules/, // 建議把 babel 的運(yùn)行時(shí)配置放在 .babelrc 里,從而與 eslint-loader 等共享配置 loader: 'babel-loader' }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'images/[name].[ext]' } } ] }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: [ { loader: 'url-loader', options: { limit: 8192, mimetype: 'application/font-woff', name: 'fonts/[name].[ext]' } } ] }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: [ { loader: 'file-loader', options: { limit: 8192, mimetype: 'application/font-woff', name: 'fonts/[name].[ext]' } } ] } ] }, plugins: [ // 每次打包前,先清空原來(lái)目錄中的內(nèi)容 new CleanWebpackPlugin([ASSETS_BUILD_PATH], { verbose: false }), // 啟用 CommonChunkPlugin new webpack.optimize.CommonsChunkPlugin({ names: 'vendor', minChunks: Infinity }) ] };
2. webpack.dev.config.js
這是用于開(kāi)發(fā)環(huán)境的 Webpack 配置,繼承自 base:
const webpack = require('webpack'); // 讀取同一目錄下的 base config const config = require('./webpack.base.config'); // 添加 webpack-dev-server 相關(guān)的配置項(xiàng) config.devServer = { contentBase: './', publicPath: '/assets/' }; // 有關(guān) Webpack 的 API 本地代理,另請(qǐng)參考 https://webpack.github.io/docs/webpack-dev-server.html#proxy config.module.rules.push( { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ], exclude: /node_modules/ } ); // 真實(shí)場(chǎng)景中,React、jQuery 等優(yōu)先走全站的 CDN,所以要放在 externals 中 config.externals = { react: 'React', 'react-dom': 'ReactDOM' }; // 添加 Sourcemap 支持 config.plugins.push( new webpack.SourceMapDevToolPlugin({ filename: '[file].map', exclude: ['vendor.js'] // vendor 通常不需要 sourcemap }) ); // Hot module replacement Object.keys(config.entry).forEach((key) => { // 這里有一個(gè)私有的約定,如果 entry 是一個(gè)數(shù)組,則證明它需要被 hot module replace if (Array.isArray(config.entry[key])) { config.entry[key].unshift( 'webpack-dev-server/client?http://0.0.0.0:8080', 'webpack/hot/only-dev-server' ); } }); config.plugins.push( new webpack.HotModuleReplacementPlugin() ); module.exports = config;
3. webpack.config.js
這是用于生產(chǎn)環(huán)境的 webpack 配置,同樣繼承自 base:
const webpack = require('webpack'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 讀取同一目錄下的 base config const config = require('./webpack.base.config'); config.module.rules.push( { test: /\.less$/, use: ExtractTextPlugin.extract( { use: [ 'css-loader', 'less-loader' ], fallback: 'style-loader' } ), exclude: /node_modules/ } ); config.plugins.push( // 官方文檔推薦使用下面的插件確保 NODE_ENV new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }), // 啟動(dòng) minify new webpack.LoaderOptionsPlugin({ minimize: true }), // 抽取 CSS 文件 new ExtractTextPlugin({ filename: '[name].css', allChunks: true, ignoreOrder: true }) ); module.exports = config;
現(xiàn)在在你的工程文件夾里應(yīng)該已經(jīng)有三個(gè) Webpack 配置文件,它們分別是:
· webpack.base.config.js
· webpack.dev.config.js
· webpack.config.js
最后,你還需要在 package.json 里添加相應(yīng)的配置:
{ ... "scripts": { "build": "webpack --optimize-minimize", "dev": "webpack-dev-server --config webpack.dev.config.js", "start": "npm run dev" // 或添加你自己的 start 邏輯 }, ... }
和很多項(xiàng)目一樣,在開(kāi)發(fā)環(huán)境下的時(shí)候,你需要使用 npm run dev 來(lái)啟動(dòng),而在生產(chǎn)環(huán)境中,則用 npm run build 來(lái)發(fā)布。
題外話,在真實(shí)場(chǎng)景中,我們不會(huì)直接使用 webpack-dev-server,而采用 express + webpack/webpack-dev-middleware,配置方法與上面所述的完全相同。
關(guān)于專(zhuān)欄
如果你喜歡這篇文章,就請(qǐng)關(guān)注我的專(zhuān)欄《前端零?!?/strong>,在這里我們一起聊一聊前端技術(shù)和前端工程。
關(guān)于作者
Henry,10 歲開(kāi)始學(xué)習(xí)計(jì)算機(jī)編程,高二暑假獲得江蘇省青少年信息奧林匹克一等獎(jiǎng)。2000 年開(kāi)始自學(xué) JavaScript 及網(wǎng)頁(yè)制作,2006 年起正式開(kāi)始從事前端開(kāi)發(fā)工作,從此一干就是 10 多年。加入阿里巴巴前,曾在 SAP 中國(guó)研究院擔(dān)任智慧交通大數(shù)據(jù)產(chǎn)品經(jīng)理。
Github:MagicCube (Henry Li)
小結(jié)
前端從開(kāi)發(fā)到部署前都離不開(kāi) Webpack 的參與,本文結(jié)合了我們自己在開(kāi)發(fā)中碰到的種種問(wèn)題解決方案,同時(shí)借鑒了很多開(kāi)源項(xiàng)目的配置來(lái)介紹一種用 3 個(gè) JS 文件來(lái)配置 Webpack 的方法。關(guān)于本文如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!
免責(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)容。