從零到一,webpack搭建react

從零到一,webpack搭建react

來自專欄猿論3 人贊了文章

為了實現一個可定製化高的react工程,我們往往會自己搭建一個react工程。所以本文會從零開始搭建一個react腳手架工程。解釋webpack中配置的含義。

基於webpack 4.0 。包含 開發環境配置,生產環境配置,代碼分離,css提取,gzip壓縮,base64載入資源,打包分析,ssh一鍵部署等常用配置。

Dante-dan/react-sample-javascript?

github.com圖標

[TOC]

項目初始化

統一規範代碼格式

  1. 配置 .editorconfig 使得IDE的方式統一 (見代碼)
  2. 配置 .eslintrc.js 使得代碼規範統一 (見代碼)

預期功能

  1. 管理資源: 能載入css、sccc、less、以及靜態文件
  2. 管理輸出:將打包後的靜態文件輸出至static目錄下,以各自的文件類型管理
  3. dev:使用source map,方便調試時代碼定位
  4. dev:配置devServer,並配置熱替換,熱載入,自動刷新,自動打開瀏覽器,並預留proxyTable
  5. dev:設置默認打開8080,被佔用則尋找下一個空介面
  6. production:代碼分離,打包css文件,css代碼壓縮,js代碼壓縮,輸出到模板html,配置gzip
  7. analysis::使用BundleAnalyzerPlugin 分析打包後的性能

目錄結構

:.│ .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

安裝依賴

  • eslint-loader
  • eslint
  • eslint-config-airbnb
  • eslint-plugin-import
  • eslint-friendly-formatter
  • eslint-plugin-flowtype
  • eslint-plugin-jsx-a11y
  • eslint-plugin-react
  • babel-polyfill
  • webpack
  • jest
  • friendly-errors-webpack-plugin 編譯提示的webpack插件
  • html-webpack-plugin 新建html入口文件的webpack插件
  • copy-webpack-plugin webpack配置合併模塊
  • webpack-merge webpack配置合併模塊
  • 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

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 基礎配置

  1. 為了控制開發環境和生產環境,我們可以新建build文件夾。分別書寫開發環境和生產環境的webpack配置文件,這樣也更可以方便我們分別控制生產環境和開發環境。
  2. 為了提高代碼的復用率,也為了區別 基礎配置個性配置 ,可以分別新建webpack.basewebpack.devwebpack.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

entry可以分別為字元串、數組和對象。

倘若應用只有一個單一的入口,entry的值可以使用任意類型,不會影響輸出結果。

// 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.splitChunks


output

output最基礎的兩個配置為 pathfilename

- 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

resolve常用的兩個配置為 aliasextensions

- alias 創建import或者require的別名

- extensins 自動解析文件拓展名,補全文件後綴

resolve: { // 自動解析文件擴展名(補全文件後綴)(從左->右) // import hello from ./hello (!hello.js? -> !hello.jsx? -> !hello.json) extensions: [.js, .jsx, .json], alias: { @: resolve(src) } },

module

module的選項決定了如何處理項目中的不同類型的模塊。其中常用的有 rulesnoParese 兩個配置項。

- 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-lodaerbabel-loader 處理了除了node_modules 以外的 js||jsx。同時配置了,解析圖片、視頻、字體文件等的解析,當rules匹配到的文件時,小於10000 byte 時,採用url-loader解析文件。

Webpack開發配置

因為在webpack 4.X 中使用了流行的 」約定大於配置「 的做法,所以在新加入配置項 mode ,可以告知webpack使用相應模式的內置優化。

如果我們只設置NODE_ENV,則不會自動設置 mode

在開發時,我們往往希望能看到當前開發的頁面,並且能熱載入。這時,我們可以藉助webpack-dev-server 這個插件,來在項目中起一個應用伺服器。

// package.json"scripts": { "start": "webpack-dev-server --mode development --config build/webpack.dev.conf.js",}

// 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: /srccontentBase 指定了devServer能訪問的資源地址。因為我們開發時,資源大部分都放在src目錄下,所以可以直接指定資源路徑為src目錄。因為我們在webpack基礎配置時,配置了 output 輸出為 dist 目錄,所以我們也可以在devServer里,設置 contentBasedist 目錄。不過此時需要使用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 } },

可以參閱如下鏈接:

SplitChunksPlugin | webpack 中文網?

www.webpackjs.com


css代碼壓縮

藉助 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, }),


生成HTML

使用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.jsconst BundleAnalyzerPlugin = process.env.NODE_ENV=== analysis ? require(webpack-bundle-analyzer).BundleAnalyzerPlugin:nullprocess.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 ", },

通過注入環境變數,來控制是否運行打包分析。


ssh部署

打包後的dist文件夾,可以直接藉助 node 的 ssh-node ,直接部署到伺服器指定的目錄下。 ssh-node既支持ssh,也支持密碼登錄。建議可以為在每個項目下,新建一個.ssh文件,存放項目的私鑰。代碼如下:

// usage: https://www.npmjs.com/package/node-sshvar path, node_ssh, ssh, fs, opn, hostfs = require(fs)path = require(path)node_ssh = require(node-ssh)opn = new require(opn)ssh = new node_ssh()host = localhostvar localDir = ./distvar remoteDir = /opt/frontend/newvar removeCommand = rm -rf ./*var pwdCommand = pwdssh.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("Somethings wrong") console.log(error) ssh.dispose() }) }) })

此時,在命令行直接 node deploy.js 就可以運行以上腳本,我們也可以添加一個build + deploy的script腳本,便於啟動。

"scripts": { "depoly": "npm run build && node ./deploy.js",}


結語

本次從零到一,新建了一個react腳手架。過程中有很多問題,也參考了不少大牛的解釋。代碼里也有諸多問題。還望各位看官,不吝指教。

記得留下你的足跡哦。

參考

參考了vue-cli v2.96的部分webpack配置。

【翻譯】Webpack——令人困惑的地方 · Issue #13 · chemdemo/chemdemo.github.io?

github.com圖標配置 | webpack 中文網?

www.webpackjs.com


推薦閱讀:

webpack源碼學習系列之二:code-splitting(代碼切割)
sass頭腦風暴
基於webpack4.x工程配置
(webpack系列二)webpack打包優化探索
webpack運行Babel教程

TAG:前端開發 | React | webpack |