webpack實戰(二):真實專案中應用系統配置

大司馬愛學習發表於2018-06-22

字數:2061

閱讀時間:15分鐘

環境:webpack3.8.1

前言

本文續接文章: webpack實戰(一):真實專案中一個完整的webpack配置

上篇文章講的是框架的配置方案,本文講的是應用系統的配置方案。

這裡,我們先梳理一下框架和應用的關係。這裡我在框架配置中自定義了一個webpack外掛,其作用就是生成一個loader.js檔案,應用系統直接在頁面中載入該檔案,就會自動載入框架相應的資原始檔。即,我們這裡的目的是讓不同的應用系統可以使用同一份框架資源,從而減輕部署壓力。因此,我們所有的配置也是建立在這個基礎之上。

其次,由於我們的應用系統配置項大同小異,所以,所有的應用系統會有一個公共的配置檔案。

正文

應用系統的基本目錄結構如下:

-all
	-build
		-common
			-webpack.common.config.js
			-webpack.dev.config.js
			-webpack.prod.config.js
	-app1
		-build
			-webpack.config.js
	-app2
	-app3
複製程式碼

all資料夾中放置這所有的應用系統檔案。其下build資料夾放置所有應用系統的公共配置,app1、app2、app3分別表示不同的應用系統資料夾。在應用系統資料夾中,有一個build資料夾,放置應用系統的webpack配置檔案。

接下來我們就分別按照如上檔案,一一講解。

檔名 作用
all/build/common/webpack.common.config.js 公共配置中的基礎配置
all/build/common/webpack.dev.config.js 公共配置中的開發環境配置
all/build/common/webpack.prod.config.js 公共配置中的生產環境配置
app1/build/webpack.config.js 應用系統中的配置

1.all/build/common/webpack.common.config.js

公共配置中的基礎配置,先上程式碼:

const wepackMerge = require('webpack-merge');
const Path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');

const ProdConfig = require('./webpack.prod.config');
const DevConfig = require('./webpack.dev.config');

//根據條件處理相關配置
const genarateConfig = (env, dirname, options) => {
    //樣式loader
    let cssLoader = [{
        loader: 'css-loader',
        options: {
            sourceMap: true
        }
    }, {
        loader: 'postcss-loader',
        options: {
            ident: 'postcss',
            plugins: [
                require('postcss-cssnext')()
            ],
            sourceMap: true
        }
    }, {
        loader: 'less-loader',
        options: {
            sourceMap: true
        }
    }];
    let styleLoader = [{
        test: /\.(css|less)$/,
        // exclude: /(node_modules|bower_components)/,
        use: env === 'prod' ? ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: cssLoader
        }) : [{
            loader: 'style-loader',
            options: {
                sourceMap: true
            }
        }].concat(cssLoader)
    }];

    //指令碼loader
    let jsLoader = [{
        test: /\.js$/,
        exclude: /(node_modules|bower_components|(\.exec\.js))/,
        use: [{
            loader: 'babel-loader'
        }].concat(env === 'dev' ? [{
            loader: 'eslint-loader'
        }] : [])
    }, {
        test: /\.exec\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
            loader: 'script-loader'
        }
    }];

    //檔案處理loader
    let fileLoaderOptions = {
        useRelativePath: false,
        name: '[name]-[hash:5].[ext]',
        publicPath: '../'
    };
    if (env === 'prod') {
        fileLoaderOptions.limit = 8000;
    }
    let fileLoader = [{
        test: /\.(pdf)$/,
        exclude: /(node_modules|bower_components)/,
        use: [{
            loader: 'file-loader',
            options: env === 'dev' ? fileLoaderOptions : Object.assign({}, fileLoaderOptions, {
                outputPath: '../dist/pdf',
                publicPath: './pdf'
            })
        }]
    }, {
        test: /\.(jpg|jpeg|png|icon|gif)$/,
        exclude: /(node_modules|bower_components)/,
        use: [{
            loader: env === 'dev' ? 'file-loader' : 'url-loader',
            options: env === 'dev' ? fileLoaderOptions : Object.assign({}, fileLoaderOptions, {
                outputPath: '../dist/img',
                publicPath: './img'
            })
        }]
    }, {
        //解析字型檔案
        test: /\.(eot|svg|ttf|woff2?)$/,
        exclude: /(node_modules|bower_components)/,
        use: [{
            loader: env === 'dev' ? 'file-loader' : 'url-loader',
            options: env === 'dev' ? fileLoaderOptions : Object.assign({}, fileLoaderOptions, {
                outputPath: '../dist/fonts',
                publicPath: './fonts'
            })
        }]
    }, {
        //解析主頁面和頁面上的圖片
        test: /\.(html)$/,
        exclude: /(node_modules|bower_components)/,
        use: {
            loader: 'html-loader',
            options: {
                attrs: ['img:src', 'img:data-src'],
                minimize: true
            }
        }
    }];

    //webpack外掛
    let plugins = [];

    //HtmlWebpackPlugin 外掛
    let htmlWebpacks = [new HtmlWebpackPlugin({
        template: Path.join(dirname, '../index.ejs'),
        inject: true,
        filename: 'index.html',
        chunks: ['index', 'loader'],
        chunksSortMode: function (item1, item2) {
            //先載入loader
            if (item1.id === 'loader') {
                return -1;
            } else {
                return 1;
            }
        }
    })];
    options.form === true && htmlWebpacks.push(new HtmlWebpackPlugin({
        template: Path.join(dirname, '../forms/index.ejs'),
        inject: true,
        filename: 'forms/index.html',
        chunks: ['form', 'formLoader'],
        chunksSortMode: function (item1, item2) {
            //先載入loader
            if (item1.id === 'formLoader') {
                return -1;
            } else {
                return 1;
            }
        }
    }));

    htmlWebpacks = options.htmlWebpackPlugins || htmlWebpacks;
    plugins = plugins.concat(htmlWebpacks);

    //複製資源
    let copyPlugins = [
        new CopyWebpackPlugin([{
            from: './views',
            to: 'views/'
        }, {
            from: './test',
            to: 'test/'
        }], {
            ignore: ['**/.svn/**']
        })
    ];
    options.form === true && copyPlugins.push(new CopyWebpackPlugin([{
        from: './forms/views',
        to: 'forms/views'
    }], {
        ignore: ['**/.svn/**']
    }));

    copyPlugins = options.copyPlugins || copyPlugins;

    plugins = plugins.concat(copyPlugins);

    //全域性變數定義
    plugins.push(new Webpack.DefinePlugin({
        WEBPACK_DEBUG: env === 'dev'
    }));

    //友好提示外掛
    plugins.push(new FriendlyErrorsPlugin());
    //不打包預設載入項
    plugins.push(new Webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));

    let entry = {
        loader: './loader.js',
        index: './index.js'
    };

    options.form === true && (entry.form = './forms/index.js', entry.formLoader = './forms/loader.js');
    entry = options.entry == null ? entry : options.entry;

    let config = {
        devtool: 'source-map',
        context: Path.join(dirname, '../'),
        entry: entry,
        output: {
            path: Path.join(dirname, '../dist/'),
            filename: env === 'dev' ? '[name]-[hash:5].bundle.js' : '[name]-[chunkhash:5].bundle.js'
        },
        module: {
            rules: [].concat(styleLoader).concat(jsLoader).concat(fileLoader)
        },
        plugins: plugins
    };
    return config;
};

module.exports = (env, dirname, options) => {
    options = options == null ? {} : options;
    var config = env === 'dev' ? DevConfig(dirname, options) : ProdConfig(dirname, options);
    return wepackMerge(genarateConfig(env, dirname, options), config);
};

複製程式碼

這個檔案也是我們最主要的配置內容,其中大部分內容和之前的框架配置內容一致,這裡不做贅述。

這裡有區別的就是,我們在輸出的函式中,新增了一個 options 引數,這個引數就是用來傳遞不同應用系統的定製化配置的。

其中:

options.form 是我們特殊應用的一個配置內容,是強業務相關內容,可以略過。

options.htmlWebpackPlugins 是配置 HtmlWebpackPlugin 外掛的,由於不同的應用系統的模板配置會有差異,所以我們將其作為配置項傳入。

options.copyPlugins 是配置 CopyWebpackPlugin 外掛的,不同的應用系統需要複製的內容不同。

options.entry 是配置外掛入口的,不同應用系統入口不同。

這裡,是我們的業務需求導致會有這些配置,在大家各自的業務中,這塊的配置可能都不一樣。

2.all/build/common/webpack.dev.config.js

公共配置中的開發環境配置,先上程式碼:

const Webpack = require('webpack');

module.exports = (dirname, options) => {
    let gtUIPath = options.gtUIPath;
    return {
        devServer: {
            port: '9090',
            overlay: true,
            //設定為false則會在頁面中顯示當前webpack的狀態
            inline: true,
            historyApiFallback: true,
            //代理配置
            proxy: {
                '/gt-ui': {
                    target: gtUIPath,
                    changeOrigin: true,
                    logLevel: 'debug',
                    headers: {}
                }
            },
            hot: true,
            //強制頁面不通過重新整理頁面更新檔案
            hotOnly: true
        },
        plugins: [
            //模組熱更新外掛
            new Webpack.HotModuleReplacementPlugin(),
            //使用HMR時顯示模組的相對路徑
            new Webpack.NamedModulesPlugin()
        ]
    };
};
複製程式碼

這塊需要注意的就是我們傳遞了一個 options.gtUIPath地址以作代理之用。這裡主要是為了解決字型資源等跨域的問題。

3.all/build/common/webpack.prod.config.js

公共配置中的生產環境配置,先上程式碼:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackInlineChunkPlugin = require('html-webpack-inline-chunk-plugin');
const Webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Path = require('path');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const SvnInfo = require('svn-info').sync('https://218.106.122.66/svn/application/trunk/gm-ui', 'HEAD');

module.exports = (dirname, options) => {
    let pakageName = Path.basename(Path.join(dirname, '../'));
    return {
        plugins: [
            //混淆程式碼
            new UglifyJsPlugin({
                sourceMap: true,
                //多執行緒處理
                parallel: true,
                //使用快取
                cache: true
            }),
            //提取css檔案
            new ExtractTextPlugin({
                filename: '[name]-[hash:5].css'
            }),
            new CleanWebpackPlugin(['dist'], {
                root: Path.join(dirname, '../')
            }),
            new Webpack.NamedChunksPlugin(),
            new Webpack.NamedModulesPlugin(),
            //版本資訊
            new Webpack.BannerPlugin({
                banner: `SVNVersion: ${SvnInfo.revision}\nDate: ${new Date().toISOString().slice(0, 10)}`,
                raw: false,
                entryOnly: true,
                include: /\.js/g
            }),
            //壓縮資料夾
            new FileManagerPlugin({
                onEnd: {
                    mkdir: [Path.join(dirname, '../package/')],
                    archive: [{
                        source: Path.join(dirname, '../dist'),
                        destination: Path.join(dirname, `../package/${pakageName}.zip`),
                        options: {}
                    }]
                }
            })
        ]
    };
};

複製程式碼

這裡的配置與框架的配置基本一致,裡面的外掛也都有講解,這裡就不做贅述了。

4.app1/build/webpack.config.js

應用系統中的配置,先上程式碼:

const Path = require('path');
const wepackMerge = require('webpack-merge');
const commonConfig = require('../../build/common/webpack.common.config.js');

module.exports = env => {
    //通用配置
    let pConfig = commonConfig(env, __dirname, {
        form: true,
        //配置開發環境中框架的訪問地址
        gtUIPath: 'http://localhost:8020/'
    });

    //基於通用配置的調整配置
    let modifyConfig = {
        resolve: {
            alias: {
                mainCtrl: Path.join(__dirname, '../controllers/main-ctrl')
            }
        }
    };

    //返回合併後的配置
    return wepackMerge(pConfig, modifyConfig);
};

複製程式碼

由於公共的配置部分已經抽離出去了,所以這塊的配置就非常簡單了,這也是我們使用這種配置方案最大的優勢。

在這裡,我們可以通過options引數和直接 merge 相應配置來做配置上的定製化調整。

如何使用配置

在package.json中做如下配置:

{
  "scripts":{
    "app1-d":"webpack-dev-server --env dev --config ./app1/build/webpack.config.js --open",
    "app1-p":"webpack --env prod --config ./app1/build/webpack.config.js",
    "app2-d":"webpack-dev-server --env dev --config ./app2/build/webpack.config.js --open",
    "app2-p":"webpack --env prod --config ./app2/build/webpack.config.js"
  }
}
複製程式碼

這樣,我們執行對應命令即可。

以上,就是我關於webpack實戰中的完整配置方案,希望對大家有所幫助,也希望大家多多提出寶貴意見。

歡迎關注我的微信公眾號:

webpack實戰(二):真實專案中應用系統配置

相關文章