React和Vue項目的webpack配置基本思路

本篇文章是自己通過搜集資料使用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.jswebpack.prod.js兩個文件九妈。

第二步增加常量配置文件
webpack.dev.jswebpack.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.jswebpack.prod.js合并荆永。

第四步獨立build文件
為了在 build 時候方便做一些多余的處理蹦狂,比如在build的時候在控制臺使用一些提示插件提示正在打包文字提升開發(fā)體驗等等勤哗,當打包完成后取消打包中顯示塌碌。因此獨立出來一個build.js文件浪慌,專門用來執(zhí)行打包缠犀。

看下圖結(jié)構(gòu):


config文件夾配置

通過上面四步陌知,就在config文件夾下創(chuàng)建了build.js呻引,config.js考蕾,webpack.base.js祸憋,webpack.dev.jswebpack.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配置文件吧。

wepack配置規(guī)劃圖

配置文件內(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要用的PORTHOST仰坦,這里是自定義的一些東西,不必非得寫在這里计雌。假如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-loaderpostcss-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.
開啟NameChunksPluginNameModulesPlugin.
production 設(shè)置process.env.NODE_ENV的值為production.
開啟FlagDependencyUsagePlugin, FlagIncludeChunksPlugin县好,
ModileConcatentationPluginNoEmitOnErrorsPlugin趣倾,
OccurrenceOrderPlugin聘惦,SideEffectsFlagPluginTerserPlugin.
none 不開啟任何優(yōu)化選項

到這里整個webpack里面的配置文件就完成了,該文章提供一個webpack配置思路,可以用來配置Vue應(yīng)用配置善绎,也可以用來配置React應(yīng)用配置黔漂。

我會用這套配置自己寫一下個人移動端小博客,技術(shù)棧使用webpack+react+redux+typescript

webpack配置代碼庫 https://github.com/Mstian/webpack-config-react

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末禀酱,一起剝皮案震驚了整個濱河市炬守,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剂跟,老刑警劉巖减途,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曹洽,居然都是意外死亡鳍置,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門送淆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來税产,“玉大人,你說我怎么就攤上這事偷崩”倏剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵阐斜,是天一觀的道長衫冻。 經(jīng)常有香客問我,道長谒出,這世上最難降的妖魔是什么隅俘? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮笤喳,結(jié)果婚禮上考赛,老公的妹妹穿的比我還像新娘。我一直安慰自己莉测,他們只是感情好颜骤,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捣卤,像睡著了一般忍抽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上董朝,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天鸠项,我揣著相機與錄音,去河邊找鬼子姜。 笑死祟绊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牧抽,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼嘉熊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扬舒?” 一聲冷哼從身側(cè)響起阐肤,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讲坎,沒想到半個月后孕惜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡晨炕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年衫画,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓮栗。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡碧磅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遵馆,到底是詐尸還是另有隱情,我是刑警寧澤丰榴,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布货邓,位于F島的核電站,受9級特大地震影響四濒,放射性物質(zhì)發(fā)生泄漏换况。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一盗蟆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喳资,春花似錦觉吭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至节值,卻和暖如春徙硅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搞疗。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工嗓蘑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓桩皿,卻偏偏與公主長得像豌汇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子业簿,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355