本篇文章是自己通過搜集資料使用webpack4+react16+ts4搭建的一個React開發(fā)環(huán)境们镜,目的主要是學習webpack整體搭建流程币叹,以及各模塊負責內(nèi)容,然后準備使用這套環(huán)境使用react+ts+redux技術(shù)棧實現(xiàn)自己的移動端博客模狭。
本文不是使用webpack搭建ts+react環(huán)境的開發(fā)教程颈抚,是搭建的一些前期規(guī)劃與后期總結(jié)。
前期準備
首先使用npm init
初始化一個項目在項目中創(chuàng)建一個config
文件夾用來保存配置文件嚼鹉。
第一步區(qū)分環(huán)境
我的需求簡單只是需要一個開發(fā)環(huán)境一個生產(chǎn)環(huán)境贩汉。因此在config
文件夾下分別創(chuàng)建webpack.dev.js
和webpack.prod.js
兩個文件九妈。
第二步增加常量配置文件
在webpack.dev.js
和webpack.prod.js
文件中一些經(jīng)常使用的變量,比如判斷是否是dev
環(huán)境的標志變量IS_DEV
雾鬼,還有項目根路徑PROJECT_PATH
,以及devServer
里面要用的HOST
,PORT
等一些變量統(tǒng)一放到config.js
文件中宴树。
第三步獨立BASE配置文件
第一步我們區(qū)分開了開發(fā)環(huán)境和生產(chǎn)環(huán)境策菜,但是生產(chǎn)環(huán)境和開發(fā)環(huán)境有很多相同的配置項,因此將相同配置項抽出來放到同目錄下的webpack.base.js
文件中酒贬。最后使用webpack-merge
插件將webpack.base.js
文件和webpack-dev.js
合并又憨,還有將webpack.base.js
和webpack.prod.js
合并荆永。
第四步獨立build文件
為了在 build
時候方便做一些多余的處理蹦狂,比如在build
的時候在控制臺使用一些提示插件提示正在打包文字提升開發(fā)體驗等等勤哗,當打包完成后取消打包中顯示塌碌。因此獨立出來一個build.js
文件浪慌,專門用來執(zhí)行打包缠犀。
看下圖結(jié)構(gòu):
通過上面四步陌知,就在config
文件夾下創(chuàng)建了build.js
呻引,config.js
考蕾,webpack.base.js
祸憋,webpack.dev.js
,webpack.prod.js
五個文件肖卧。
這五個文件分別是干什么的上面也說清楚了蚯窥,然后說一下其他文件以及文件夾的作用吧。
config
文件夾用來存放webpack配置文件塞帐;
dist
是構(gòu)建輸出目錄拦赠;
node_modules
存放下載的node包;
public
文件夾存放一些靜態(tài)文件葵姥,以及html模板文件荷鼠;
src
文件夾用來存放開發(fā)時的代碼;
.babelrc
是babel的配置項牌里;
.npmrc
是對node包下載源的源配置颊咬,比如想用淘寶源,就在該文件中配置npm config set registry https://registry.npm.taobao.org
牡辽;這樣之后使用npm下載依賴包的時候喳篇,就默認使用淘寶源了。不用手動切換态辛。
package.json
存儲項目信息麸澜;
README.md
文件是我用來記錄項目中遇到的一些問題一些解決方法等等。
tsconfig.json
文件是typescript的配置文件奏黑。
好炊邦,整個文件目錄介紹完了编矾,現(xiàn)在就主要看webpack配置文件吧。
配置文件內(nèi)容
config.js
先從config
文件夾下的config.js
說起吧馁害,直接看代碼吧窄俏。
const path = require('path');
const IS_DEV = process.env.NODE_ENV !== 'production';
module.exports = {
PROJECT_PATH: path.resolve(__dirname, "../"),
IS_DEV,
PORT: 8000,
HOST: 'localhost'
}
這就是config.js
中所有內(nèi)容了,主要利用node的path模塊對外暴露了項目根目錄PROJECT_PATH
碘菜,還通過process.env.NODE_ENV
獲取環(huán)境變量然后判斷是否是development環(huán)境凹蜈,并導(dǎo)出IS_DEV
變量,這個環(huán)境變量問題稍后說忍啸。還導(dǎo)出了devServer要用的PORT
和HOST
仰坦,這里是自定義的一些東西,不必非得寫在這里计雌。假如devServer中的port和host完全可以就在devServer里面直接寫死悄晃,不必再從這個文件中獲取。
然后接著說一下環(huán)境變量的問題凿滤,環(huán)境變量我這里使用了cross-env
插件妈橄,然后在package.js中配置run執(zhí)行腳本命令的時候傳遞參數(shù)通過process.env.NODE_ENV
動態(tài)獲取參數(shù)內(nèi)容,來做的鸭巴。
npm i cross-env -D
package.json文件中:
{
// ...
"scripts":{
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./config/webpack.dev.js",
"build": "cross-env NODE_ENV=production node ./config/build.js"
}
// ...
}
以上就是通過cross-env
指定NODE_ENV的值之后在配置文件中可以通過process.env.NODE_ENV
來獲取眷细。
這個環(huán)境變量還要其他的設(shè)置方式,比如在本地配置環(huán)境變量文件鹃祖,可以參考creat-react-app
配置溪椎。
webpack.base.js
webpack.base.js
文件中主要做了webpack的基礎(chǔ)配置項,代碼如下:
// base
const {resolve} = require('path');
const {PROJECT_PATH, IS_DEV} = require('./config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const WebpackBar = require('webpackbar');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const getCssLoaders = () => {
return [
IS_DEV ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: IS_DEV,
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer:{
grid: true,
flexbox: 'no-2009'
},
stage: 3
}),
require('postcss-normalize')
],
sourceMap: IS_DEV
}
}
]
}
module.exports = {
entry: {
app: resolve(PROJECT_PATH, './src/index.tsx')
},
output: {
filename: `js/[name]${IS_DEV ? '' :'.[hash:8]'}.js`,
path: resolve(PROJECT_PATH, './dist')
},
module: {
rules: [
{
test: /\.(tsx|js)$/,
loader: 'babel-loader',
options: {cacheDirectory: true},
exclude: /node_modules/
},
{
test: /\.css$/,
use: getCssLoaders(),
},
{
test: /\.less$/,
use: [
... getCssLoaders(),
{
loader: 'less-loader',
options: {
sourceMap: IS_DEV
}
}
]
},
{
test: /\.scss$/,
use: [
...getCssLoaders(),
{
loader: 'sass-loader',
options: {
sourceMap: IS_DEV
}
}
]
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024,
name: '[name].[contenthash:8].[ext]',
outputPath: 'images'
}
}
]
},
{
test: /\.(ttf|woff|woff2|eot|otf)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name].[contenthash:8].[ext]',
outputPath: 'fonts'
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: resolve(PROJECT_PATH, './public/index.html'),
filename: 'index.html',
cache: false,
minify: IS_DEV ? false : {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
useShortDoctype: true,
}
}),
new CopyPlugin({ // 拷貝靜態(tài)資源
patterns: [
{
context: resolve(PROJECT_PATH, './public'),
from: '*',
to: resolve(PROJECT_PATH, './dist'),
toType: 'dir'
},
{
context: resolve(PROJECT_PATH, './public/static'),
from: '*',
to: resolve(PROJECT_PATH, './dist/static'),
toType: 'dir'
}
]
}),
new WebpackBar({ // 顯示啟動進度
name: IS_DEV ? '正在啟動' : '正在打包'
}),
new ForkTsCheckerWebpackPlugin(), // 編譯時typescript類型檢查
],
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: {
'Src': resolve(PROJECT_PATH, './src'),
'Components': resolve(PROJECT_PATH, './src/components'),
'Containers': resolve(PROJECT_PATH, './src/containers')
}
},
devtool: IS_DEV ? 'cheap-module-eval-source-map' : 'cheap-module-source-map'
}
常用的導(dǎo)入以及配置細節(jié)就不多說了恬口,都是遵循webpack配置規(guī)則校读,有配置疑問的可以在webpack官網(wǎng)或者npm官網(wǎng)查資料,看一下里面有個getCssLoaders
函數(shù)祖能,如下:
const getCssLoaders = () => {
return [
IS_DEV ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: IS_DEV,
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer:{
grid: true,
flexbox: 'no-2009'
},
stage: 3
}),
require('postcss-normalize')
],
sourceMap: IS_DEV
}
}
]
}
這個函數(shù)的作用主要是將loader配置中的公共部分 style-loader
歉秫,css-loader
,postcss-loader
這些配置提取出來养铸,這樣的話就可以將css,less,sass中多余的配置項都抽取出來雁芙,也可以將該函數(shù)放到外面的config.js文件中去維護,尤其是base.js文件特別多的時候钞螟,目前我就暫且這么放了兔甘。
webpack.dev.js
在dev環(huán)境中最重要的就是devServer了,因此在該文件中主要做了devServer的配置還有熱更新的配置鳞滨。
const {merge} = require('webpack-merge');
const baseConfig = require('./webpack.base');
const {PORT, HOST} = require('./config');
const webpack = require('webpack');
module.exports = merge(baseConfig, {
mode: 'development',
devServer: {
host: HOST,
port: PORT,
open: true,
hot: true,
stats: 'errors-only', // 終端僅打印 error
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
當該文件配置內(nèi)容完成時洞焙,需要將最終結(jié)果導(dǎo)出的時候,這個時候需要將base的配置和當前dev配置進行merge然后再導(dǎo)出。這里使用了webpack.HotModuleReplacementPlugin插件澡匪。同時在使用熱更新插件時熔任,需要在項目入口文件(也就是src/index.tsx)中添加如下代碼:
if ((module as any) && (module as any).hot) {
// 熱更新設(shè)置 as any解決 Property 'hot' does not exist on type 'NodeModule'.
(module as any).hot.accept();
}
webpack.prod.js
在生產(chǎn)環(huán)境比較重要的就是代碼體積壓縮,分包優(yōu)化唁情,緩存處理疑苔。更好的支持tree-shaking等。
const {merge} = require('webpack-merge');
const {resolve} = require('path');
const {PROJECT_PATH} = require('./config');
const baseConfig = require('./webpack.base');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(), // 清理構(gòu)建產(chǎn)物
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css',
ignoreOrder: false
}),
new PurgeCSSPlugin({ // 剔除沒有用到的css樣式
paths: glob.sync(`${resolve(PROJECT_PATH, './src')}/**/*.{tsx,scss,less,css}`, {nodir: true}),
whitelist: ['html', 'body']
})
],
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin({ // js壓縮
extractComments: false,
terserOptions: {
compress: {
pure_funcs: ['console.log']
}
}
}),
new OptimizeCSSAssetsPlugin() // css壓縮整合
].filter(Boolean),
splitChunks:{ // 分包優(yōu)化
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: false,
cacheGroups: {
vendor: {
name: 'vendor',
chunks: 'initial',
priority: -10,
reuseExistingChunk: false,
test: /node_modules\/(.*)\.js/
},
styles: {
name: 'styles',
test: /\.(scss|css)$/,
chunks: 'all',
minChunks: 1,
reuseExistingChunk: true,
enforce: true
}
}
}
}
})
以上文件中常用的生產(chǎn)環(huán)境內(nèi)容簡單的說一下甸鸟。
clean-webpack-plugin
插件用來在每次build之前自動清理構(gòu)建產(chǎn)物夯巷。
mini-css-extract-plugin
插件用來將css內(nèi)容提取出來到一個.css文件中(使用style-loader的css樣式文件是默認被插入到html文件的style標簽里的,這樣不利于做緩存)哀墓,然后加入文件指紋(hash,chunkHash喷兼,contentHash)可以用來配合瀏覽器做樣式緩存篮绰。
purgecss-webpack-plugin
插件主要用來剔除在文件中沒有用到的樣式內(nèi)容,類似于tree-shaking將死代碼剔除掉季惯》透鳎可以減小代碼體積。
terser-webpack-plugin
插件主要是webpack4中用來替換UglifyJs插件勉抓,更好支持ES6語法壓縮贾漏,也可以額外配置多進程壓縮。
optimize-css-assets-webpack-plugin
插件主要是對css樣式文件進行壓縮處理藕筋。
splitChunks
中的一些配置項主要是針對被多次引用文件纵散,體積較大文件進行分包單獨抽離,減小文件打包體積隐圾。
build.js
將build的文件單獨拎出來伍掀,在執(zhí)行webpack函數(shù)時可以做一些額外操作。
const ora = require('ora')
const webpack = require('webpack');
const webpackConfig = require('./webpack.prod.js');
const spinner = ora('building for production...')
spinner.start()
webpack(webpackConfig, (err, stats) => {
spinner.stop()
})
比如在build的時候暇藏,執(zhí)行提示蜜笤,執(zhí)行完成之后提示消失。
還有就是配置文件中的mode盐碱,它不僅僅是用來區(qū)分開發(fā)環(huán)境與生產(chǎn)環(huán)境的把兔,而是在不同的模式下webpack會默認根據(jù)不同模式執(zhí)行不同的內(nèi)置函數(shù)。執(zhí)行內(nèi)置函數(shù)對項目進行優(yōu)化等操作瓮顽。
設(shè)置mode可以自動觸發(fā)webpack中的某些函數(shù)
Mode的內(nèi)置函數(shù)功能
選項 | 描述 |
---|---|
development | 設(shè)置 process.env.NODE_ENV 的值為development .開啟 NameChunksPlugin 和NameModulesPlugin . |
production | 設(shè)置process.env.NODE_ENV 的值為production .開啟 FlagDependencyUsagePlugin , FlagIncludeChunksPlugin 县好,ModileConcatentationPlugin ,NoEmitOnErrorsPlugin 趣倾,OccurrenceOrderPlugin 聘惦,SideEffectsFlagPlugin 和TerserPlugin . |
none | 不開啟任何優(yōu)化選項 |
到這里整個webpack里面的配置文件就完成了,該文章提供一個webpack配置思路,可以用來配置Vue應(yīng)用配置善绎,也可以用來配置React應(yīng)用配置黔漂。
我會用這套配置自己寫一下個人移動端小博客,技術(shù)棧使用webpack+react+redux+typescript
webpack配置代碼庫 https://github.com/Mstian/webpack-config-react