爲了實現一個可定製化高的react工程,咱們每每會本身搭建一個react工程。因此本文會從零開始搭建一個react腳手架工程。解釋webpack中配置的含義。
基於webpack 4.0 。包含 開發環境配置,生產環境配置,代碼分離,css提取,gzip壓縮,base64加載資源,打包分析,ssh一鍵部署等經常使用配置。
github項目地址
[TOC]javascript
.editorconfig
使得IDE的方式統一 (見代碼).eslintrc.js
使得代碼規範統一 (見代碼):. │ .babelrc #babel的規則以及插件 │ .editorconfig #IDE/編輯器相關的配置 │ .eslintignore #Eslint忽視的目錄 │ .eslintrc.js #Eslint的規則和插件 │ .gitignore #Git忽視的目錄 │ .postcssrc.js #postcss的插件 │ package-lock.json │ package.json #項目相關的包 │ README.md │ yarn.lock │ ├─build #webpack相關的配置 │ utils.js #webpack配置中的通用方法 │ webpack.base.conf.js #webpack的基礎配置 │ webpack.dev.conf.js #webpack的開發環境配置 │ webpack.prod.conf.js #webpack的生產環境配置 │ └─src #主目錄,業務代碼 │ app.css │ App.js │ favicon.ico │ index.ejs │ index.js │ └─assets #靜態目錄,存放靜態資源 │ config.json │ └─img logo.svg
編譯提示的webpack插件
新建html入口文件的webpack插件
webpack配置合併模塊
webpack配置合併模塊
yarn add eslint eslint-loader eslint-config-airbnb eslint-plugin-import eslint-friendly-formatter eslint-plugin-flowtype eslint-plugin-jsx-a11y eslint-plugin-react babel-polyfill webpack jest webpack-merge copy-webpack-plugin html-webpack-plugin friendly-errors-webpack-plugin webpack-dev-server webpack-bundle-analyzer webpack-cli portfinder extract-text-webpack-plugin node-notifier optimize-css-assets-webpack-plugin autoprefixer mini-css-extract-plugin autoprefixer css-loader less-loader postcss-loader postcss-import postcss-loader style-loader babel-core babel-eslint babel-loader babel-plugin-transform-runtime babel-plugin-import babel-preset-env babel-preset-react babel-polyfill url-loader cross-env file-loader -D
基礎配置
和 個性配置
,能夠分別新建webpack.base
、webpack.dev
和 webpack.prod
三個配置文件。首先配置最基礎的entry(入口)和output(出口)。module.exports = { context: path.resolve(__dirname, '../'), //絕對路徑。__dirname爲當前目錄。 //基礎目錄用於從配置中解析入口起點。由於webpack配置在build下,因此傳入 '../' entry: { app: ('./src/index.js') //項目的入口 }, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[hash:8].js', publicPath: '/', libraryTarget: 'umd', }, }
entry能夠分別爲字符串、數組和對象。css
假若應用只有一個單一的入口,entry的值可使用任意類型,不會影響輸出結果。html
// entry爲字符串 { entry: './src/index.js', output: { path: '/dist', filename: 'bundle.js' } } // 結果會生成 '/dist/bundle.js'
// entry爲數組,能夠添加多個彼此不互相依賴的文件。結合output.library選項,若是傳入數組,則只導出最後一項。 { //若是你在html文件裏引入了'bable-polyfill',能夠經過數組將它加到bundle.js的最後。 entry: ['./src/index.js', 'babel-polyfill'] , output:{ path: '/dist', filename: 'bundle.js' } }
// entry爲對象,能夠將頁面配置爲多頁面的而不是SPA,有多個html文件。經過對象告訴webpack爲每一個入口,成一個bundle文件。 // 多頁面的配置,可能還要藉助於HtmlWebpackPlugin,來指定每一個html須要引入的js { entry: { index: './src/index.js' main: './src/index.js' login: './src/login.js' } output:{ path: '/dist/pages' filename: '[name]-[hash:5].js' //文件名取自'entry'對象的鍵名,爲了防止推送代碼後瀏覽器讀緩存,故再生成的文件以後加上hash碼。 } } // 會分別生成index.js,main.js,login.js三個文件
關於 webpack構建多頁面 能夠參考這篇文章。不過如今webpack4.x也是一次斷崖式升級,感興趣的同窗能夠自行搜索。前端
// entry也能夠傳入混合類型 { entry:{ vendor: ['jquery','amap','babel-polyfill'] //也能夠藉助CommonsChunkPlugin提取vendor的chunk。 index: './src/index.js' } output: { path: '/dist' filename: '[name]-[hash:5].js' } }
CommonsChunkPlugin在webpack4.0以後移除了,可使用splitChunksPlugin代替。能夠參閱以下連接:optimization.splitChunksjava
output最基礎的兩個配置爲 path
和 filename
:node
path
告訴 webpack的輸出目錄在那裏,通常咱們會設置在根目錄的 dist
文件夾;filename
用於指定輸出文件的文件名,若是配置了建立了多個單獨的 chunk
則可使用 [name].[hash]
這種佔位符來確保每一個文件有惟一的名稱;publicPath
則是用於更加複雜的場景。舉例:在本地時,你可能會使用 ../assets/test.png
這種url來載入圖片。而在生產環境下,你可能會使用CDN或者圖牀的地址。那麼就須要配置 publicPath = "http://cdn.example.com/assets/"
來實現生產模式下編譯輸出文件時自動更新url。output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[hash:8].js', publicPath: '/', },
resolve經常使用的兩個配置爲 alias
和 extensions
:react
alias
建立import或者require的別名extensins
自動解析文件拓展名,補全文件後綴resolve: { // 自動解析文件擴展名(補全文件後綴)(從左->右) // import hello from './hello' (!hello.js? -> !hello.jsx? -> !hello.json) extensions: ['.js', '.jsx', '.json'], alias: { '@': resolve('src') } },
module的選項決定了如何處理項目中的不一樣類型的模塊。其中經常使用的有 rules
和 noParese
兩個配置項。jquery
noParese
是爲了防止weback解析與全部與rule相匹配的文件。目的是,忽略大型的library能夠提升構建性能。noParse: function(content) { return /jquery|lodash/.test(content); }
rules
用於在建立模塊是,匹配規則數組,以肯定哪些規則可以對module應用loader,或者是修改parser。module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, enforce: 'pre', use: [{ loader: 'babel-loader', }, { loader: 'eslint-loader', // 指定啓用eslint-loader options: { formatter: require('eslint-friendly-formatter'), emitWarning: false } }] }, { test: /\.css$/, include: /node_modules/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { plugins: () => [autoprefixer({ browsers: 'last 5 versions' })], sourceMap: false, }, }, ], }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: ('assets/img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: ('assets/media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: ('assets/fonts/[name].[hash:7].[ext]') } } ] }
例如上述代碼,就使用eslint-lodaer
和 babel-loader
處理了除了node_modules
之外的 js||jsx
。同時配置了,解析圖片、視頻、字體文件等的解析,當rules匹配到的文件時,小於10000 byte 時,採用url-loader解析文件。webpack
由於在webpack 4.X 中使用了流行的 」約定大於配置「 的作法,因此在新加入配置項 mode
,能夠告知webpack使用相應模式的內置優化。git
選項 | 描述 |
---|---|
development |
會將process.env.NODE_ENV 的值設爲 development 。啓用 NamedChunksPlugin 和 NamedMoudulesPlugin 。 |
production |
會將process.env.NODE_ENV 的值設爲 production 。啓用 FlagDependencyUsagePlugin ,FlagIncludedChunksPlugin ,ModuleConcatenationPlugin ,NoEmitOnErrorsPlugin ,OccurrenceOrderPlugin ,SideEffectsFlagPlugin 和UglifyJsPlugin 。 |
若是咱們只設置NODE_ENV,則不會自動設置
mode
在開發時,咱們每每但願能看到當前開發的頁面,而且能熱加載。這時,咱們能夠藉助webpack-dev-server 這個插件,來在項目中起一個應用服務器。
// package.json "scripts": { "start": "webpack-dev-server --mode development --config build/webpack.dev.conf.js", } // 設置當前的mode爲development,一樣這個配置也能夠寫在webpack.dev.conf.js中。而後使用build目錄下的webpack.dev.conf.js 來配置相關的webpack。
devServer: { clientLogLevel: 'warning', historyApiFallback: true, //在開發單頁應用時很是有用,它依賴於HTML5 history API,若是設置爲true,全部的跳轉將指向index.html contentBase: path.resolve(__dirname, '../src'), compress: true, hot: true, // 熱加載 inline: true, //自動刷新 open: true, //自動打開瀏覽器 host: HOST||'localhost', port: PORT, overlay: { warnings: false, errors: true }, // 在瀏覽器上全屏顯示編譯的errors或warnings。 publicPath: '/', proxy: {}, quiet: true, // necessary for FriendlyErrorsPlugin // 終端輸出的只有初始啓動信息。 webpack 的警告和錯誤是不輸出到終端的 watchOptions: { poll: false } }, plugins: [ new webpack.DefinePlugin({ ...process.env }), //開啓HMR(熱替換功能,替換更新部分,不重載頁面!) new webpack.HotModuleReplacementPlugin(),// HMR shows correct file names in console on update. //顯示模塊相對路徑 new webpack.NamedModulesPlugin(), //不顯示錯誤信息 new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin ]
其實在開發時,咱們能夠設置 contentBase: '/src'
,contentBase
指定了devServer能訪問的資源地址。由於咱們開發時,資源大部分都放在src
目錄下,因此能夠直接指定資源路徑爲src
目錄。由於咱們在webpack基礎配置時,配置了 output
輸出爲 dist
目錄,因此咱們也能夠在devServer裏,設置 contentBase
爲 dist
目錄。不過此時須要使用copyWebpackPlugin將一些靜態資源複製到 dist
目錄下,手動新建dist目錄,並複製也能夠。
另外,當使用 history 路由時,要配置 historyApiFallback = true
,以此讓服務器放棄路由權限,交由前端路由。而是用 hash 路由則不須要此配置。
在使用webpack 4.x 的 mode 配置以後,須要咱們手動配置的項已經減小了不少,像js代碼壓縮這種工具 UglifyJsPlugin
就已經不用手動去配置。可是像不少前面提到的 代碼分離
、css代碼提取和壓縮
、html的生成
以及 複製靜態資源
還須要咱們手動配置。
// 設置代碼分離的輸出目錄 output: { path: path.resolve(__dirname, '../dist'), filename: ('js/[name].[hash:8].js'), chunkFilename: ('js/[name]-[id].[hash:8].js') }, // 代碼分離 optimization: { runtimeChunk: { name: "manifest" }, splitChunks: { chunks: 'all' } },
能夠參閱以下連接: optimization.splitChunks
藉助 MiniCssExtractPlugin
來實現壓縮css和提取css。由於 MiniCssExtractPlugin
沒法與style-loader 共存,因此咱們須要判斷當前環境是生成環境仍是開發環境。
咱們能夠新建一個util.js的文件,在webpack當中一些共用的方法。考慮使用個別配置字段 extract
來配置使用何種方式來配置css-loader。參見 util.js
代碼。
new MiniCssExtractPlugin({ filename: 'css/[name].[hash:8].css', chunkFilename: 'css/[name]-[id].[hash:8].css', }),
使用htmlWebpackPlugin
,配合ejs。可使控制html 的生成。經過配置的方式,生成html。由於 HtmlWebpackPlugin
自己能夠解析ejs,因此不須要單獨引入ejs的loader。
new HtmlWebpackPlugin({ filename: 'index.html', template: './src/index.ejs', // 設置目錄 title: 'React Demo', inject: true, // true->'head' || false->'body' minify: { //刪除Html註釋 removeComments: true, //去除空格 collapseWhitespace: true, //去除屬性引號 removeAttributeQuotes: true // more options: // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency' }),
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="robots" content="noindex, nofollow"> <title><%= htmlWebpackPlugin.options.title %></title> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon"> <% for (var chunk in htmlWebpackPlugin.files.css) { %> <link rel="preload" href="<%= htmlWebpackPlugin.files.css[chunk] %>" as="style"> <% } %> <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> <link rel="preload" href="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>" as="script"> <% } %> <base href="/"> </head> <body> <div id="root"></div> </body> <style type="text/css"> body { font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif; } </style> </html>
將因此可能被請求的靜態文件,分別放在assets目錄下。那麼在打包後,爲了保證目錄能正常訪問(不使用CDN等加載靜態資源時),咱們能夠配置 publicPath = '/'
。而後藉助於 CopyWebpackPlugin
實現資源複製。
new CopyWebpackPlugin([{ from: './src/assets/', to: 'assets' }]),
將 src/assets
複製到 dist/assets
目錄下。
藉助插件 BundleAnalyzerPlugin
直接在plugins中建立該插件:
// webpack.prod.conf.js const BundleAnalyzerPlugin = process.env.NODE_ENV=== 'analysis' ? require('webpack-bundle-analyzer').BundleAnalyzerPlugin:null process.env.NODE_ENV=== 'analysis' ? new BundleAnalyzerPlugin() : ()=>{}
在package.json 中可作以下配置:
"scripts": { "analysis": "cross-env NODE_ENV=analysis webpack -p --mode production --progress --config ./build/webpack.prod.conf.js ", },
經過注入環境變量,來控制是否運行打包分析。
打包後的dist文件夾,能夠直接藉助 node 的 ssh-node ,直接部署到服務器指定的目錄下。 ssh-node既支持ssh,也支持密碼登陸。建議能夠爲在每一個項目下,新建一個.ssh文件,存放項目的私鑰。代碼以下:
// usage: https://www.npmjs.com/package/node-ssh var path, node_ssh, ssh, fs, opn, host fs = require('fs') path = require('path') node_ssh = require('node-ssh') opn = new require('opn') ssh = new node_ssh() host = 'localhost' var localDir = './dist' var remoteDir = '/opt/frontend/new' var removeCommand = 'rm -rf ./*' var pwdCommand = 'pwd' ssh.connect({ host: host, username: 'root', port: 22, // password, privateKey: "./.ssh/id_rsa", }) .then(function() { ssh.execCommand(removeCommand, { cwd:remoteDir }).then(function(result) { console.log('STDOUT: ' + result.stdout) console.log('STDERR: ' + result.stderr) ssh.putDirectory(localDir, remoteDir).then(function() { console.log("The File thing is done") ssh.dispose() opn('http://'+host, {app:['chrome']}) }, function(error) { console.log("Something's wrong") console.log(error) ssh.dispose() }) }) })
此時,在命令行直接 node deploy.js
就能夠運行以上腳本,咱們也能夠添加一個build + deploy的script腳本,便於啓動。
"scripts": { "depoly": "npm run build && node ./deploy.js", }
本次從零到一,新建了一個react腳手架。過程當中有不少問題,也參考了很多大牛的解釋。代碼裏也有諸多問題。還望各位看官,不吝指教。