為了實現一個可定製化高的react工程,我們往往會自己搭建一個react工程。所以本文會從零開始搭建一個react腳手架工程。解釋webpack中配置的含義。 基於webpack 4.0 。包含 開發環境配置,生產環境配置,代碼分離,css提取,gzip壓縮,base64載入資源,打包分析,ssh一... ...
目錄
react-sample-javascript
React 16.0 boilerplate with react-router-dom, redux & webpack 4. (for javascript)
github項目地址
項目初始化
統一規範代碼格式
- 配置
.editorconfig
使得IDE的方式統一 (見代碼) 配置
.eslintrc.js
使得代碼規範統一 (見代碼)預期功能
- 管理資源: 能載入css、sccc、less、以及靜態文件
- 管理輸出:將打包後的靜態文件輸出至static目錄下,以各自的文件類型管理
- dev:使用source map,方便調試時代碼定位
- dev:配置devServer,並配置熱替換,熱載入,自動刷新,自動打開瀏覽器,並預留proxyTable
- dev:設置預設打開8080,被占用則尋找下一個空介面
- production:代碼分離,打包css文件,css代碼壓縮,js代碼壓縮,輸出到模板html,配置gzip
analysis::使用BundleAnalyzerPlugin 分析打包後的性能
目錄結構
- 首先使用npm init 初始化一個包含package.json的根目錄
:.
│ .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 基礎配置
- 為了控制開發環境和生產環境,我們可以新建build文件夾。分別書寫開發環境和生產環境的webpack配置文件,這樣也更可以方便我們分別控制生產環境和開發環境。
- 為了提高代碼的復用率,也為了區別
基礎配置
和個性配置
,可以分別新建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
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最基礎的兩個配置為 path
和 filename
:
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常用的兩個配置為 alias
和 extensions
:
alias
創建import或者require的別名extensins
自動解析文件拓展名,補全文件尾碼
resolve: {
// 自動解析文件擴展名(補全文件尾碼)(從左->右)
// import hello from './hello' (!hello.js? -> !hello.jsx? -> !hello.json)
extensions: ['.js', '.jsx', '.json'],
alias: {
'@': resolve('src')
}
},
module
module的選項決定瞭如何處理項目中的不同類型的模塊。其中常用的有 rules
和 noParese
兩個配置項。
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解析文件。(因為base64會讓圖片的體積變大,所以當文件較大時,使用base64並不明智)
Webpack開發配置
因為在webpack 4.X 中使用了流行的 ”約定大於配置“ 的做法,所以在新加入配置項 mode
,可以告知webpack使用相應模式的內置優化。
選項 | 描述 |
---|---|
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
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.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 ",
},
通過註入環境變數,來控制是否運行打包分析。
ssh部署
打包後的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腳手架。過程中有很多問題,也參考了不少大牛的解釋。代碼里也有諸多問題。還望各位看官,不吝指教。
記得留下你的足跡哦。
參考
參考了vue-cli v2.96的webpack配置。
【翻譯】Webpack——令人困惑的地方
webpack中文文檔