【webpack系列】从零搭建 webpack4+react 脚手架,四

经过三个章节的学习,你已经学会搭建了一个基于webpack4的react脚手架。如果要更改配置,比如,你希望把编译后的js文件和css文件等单独放dist下的static目录下,你想想,是不是有点麻烦。你要去浏览webpack的配置文件,找到哪些配置项,然后去更改它,我们希望有个参数配置文件,只要更改参数配置,而无需更改webpack的配置文件。

1 添加参数配置文件

(1)在根目录创建config文件夹,在config文件夹内新建一个index.js文件,文件内容如下:
    'use strict'

    const path = require('path')

    module.exports = {
        dev: {
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',
        },
        build: {
            assetsRoot: path.resolve(__dirname, '../dist'),
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',
        }
    }

我们定义了一部分配置参数,顾名思义,dev属性下的参数配置是针对开发环境,build属性下的参数配置是针对生产环境的。其中,assetsRoot是编译后的文件存放根路径,assetsSubDirectory是资源文件编译后存放的文件夹名称,assetsPublicPath是公共的路径。

(2)修改webpack.base.conf.js,如下
    const path = require('path');
    const config=require('../config');
    const APP_PATH = path.resolve(__dirname, '../app');

    module.exports = {
        entry: {
            app: './app/index.js',
            framework: ['react', 'react-dom'],
        },
        output: {
            path: config.build.assetsRoot,
            filename: '[name].js',
            publicPath: process.env.NODE_ENV === 'production'
              ? config.build.assetsPublicPath
              : config.dev.assetsPublicPath
          },
        module: {
            rules: [
                {
                    test: /\.js?$/,
                    use: "babel-loader",
                    include: APP_PATH
                }
            ]
        }
    };

注意:我们在webpack.base.conf.js内配置了filename,那么dev环境下默认使用该配置,可以删除webpack.dev.conf.js内关于output的配置。

(3)路径生成方法:

路径的配置需要由assetsPublicPath和assetsSubDirectory以及具体的子路径组成,我们写一个公共的方法来生成路径。无论是开发环境还是生产环境,我们都可以复用该方法。我们在build文件夹下建立一个utils.js文件。内容如下:

    const path = require('path')
    const config = require('../config')

    exports.assetsPath = function (_path) {
        const assetsSubDirectory = process.env.NODE_ENV === 'production'
            ? config.build.assetsSubDirectory
            : config.dev.assetsSubDirectory

        return path.posix.join(assetsSubDirectory, _path)
    }
(4)修改webpack.prod.conf.js

在webpack.prod.conf.js页面头部引入

    const config=require('../config');
    const utils=require('./utils');

修改output属性的内容

     output: {
            path: config.build.assetsRoot,
            filename: utils.assetsPath('js/[name].[chunkhash:16].js'),
            chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
        },

修改plugins内的相关配置:

    new CleanWebpackPlugin([config.build.assetsRoot], { allowExternal: true }),

    //导出css 
    new MiniCssExtractPlugin({
        filename: utils.assetsPath('css/[name].[hash].css'),
        chunkFilename: utils.assetsPath('css/[id].[hash].css'),
    }),
(5)执行编译命令查看
npm run build

查看编译后的文件是否放在static目录下了。你可以修改参数配置文件,然后试试看吧。

2 更多的配置化

接下去我们会把更多的参数和变量进行配置化。

(1)index.html HTML模板的位置

在config.js文件的dev和build参数下都配置index属性:

index: path.resolve(__dirname, '../public/index.html'),

修改HtmlWebpackPlugin内template的值:

webpack.prod.conf.js的相关修改:

     new HtmlWebpackPlugin({
                template: config.build.index,
                inject: 'body',
                minify: {
                    removeComments: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true
                },
            }),

webpack.dev.conf.js的相关修改:

    new HtmlWebpackPlugin({
            template: config.dev.index,
            inject: true
    }),
(2)增加devServer 的配置

dev环境下,启动的端口号,代理相关,以及是否自动打开浏览器等等推荐可以放在参数配置文件内。

在config.js文件内增加相关配置参数:

    proxyTable: {},
    host: 'localhost',
    port: 8080, 
    autoOpenBrowser: true,

修改webpack.dev.conf.js内的相关配置:

     devServer: {
            host: config.dev.host,
            port: config.dev.port,
            contentBase: path.join(__dirname, '../public'),
            compress: true,
            historyApiFallback: true,
            hot: true,
            https: false,
            noInfo: true,
            open: config.dev.autoOpenBrowser,
            proxy: config.dev.proxyTable,
    }

更多的css loader加载器

我们在loader中配置了less加载器,如果我们要支持其他呢,比如sass,scss等等,显然,我们需要在rules数组增加配置。其实,这里可以写成公共的生成方法。并且我们把部分参数变成可配置。比如:是否开启cssModule等。

(1)前期准备,增加部分参数配置

修改config.js内的配置:

    const path = require('path')

    module.exports = {
        base: {
            // 是否开启cssModule
            cssModule: true,
            // cssModule排除的目录, 其他css库可以放这里
            cssModuleExcludePath: /public/
        },
        dev: {
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',

            index: path.resolve(__dirname, '../public/index.html'),

            proxyTable: {},

            host: 'localhost',
            port: 8080,
            autoOpenBrowser: true,
            // 是否生成sourceMap
            cssSourceMap: true,

        },
        build: {
            assetsRoot: path.resolve(__dirname, '../dist'),
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',

            index: path.resolve(__dirname, '../public/index.html'),

            // 是否生成sourceMap
            productionSourceMap: true,
        }
    }
(2) 在utils.js内增加cssLoaders和styleLoaders方法:
    exports.cssLoaders = function (options) {
        options = options || {}

        let cssLoader = {
            loader: 'css-loader',
            options: {
                importLoaders: 1,
                sourceMap: options.sourceMap
            }
        }
        if (options.cssModule) {
            cssLoader.options.modules = true;
            cssLoader.options.localIdentName = '[local]__[hash:7]';
        }
        const postcssLoader = {
            loader: 'postcss-loader',
            options: {
                sourceMap: options.sourceMap
            }
        }
        function generateLoaders(loader, loaderOptions) {
            const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

            if (loader) {
                loaders.push({
                    loader: loader + '-loader',
                    options: Object.assign({}, loaderOptions, {
                        sourceMap: options.sourceMap
                    })
                })
            }

            if (options.extract) {
                return [MiniCssExtractPlugin.loader].concat(loaders)
            } else {
                return ['style-loader'].concat(loaders)
            }
        }

        return {
            css: generateLoaders(),
            postcss: generateLoaders(),
            less: generateLoaders('less', { javascriptEnabled: true, indentedSyntax: true }),
            sass: generateLoaders('sass', { indentedSyntax: true }),
            scss: generateLoaders('sass'),
            stylus: generateLoaders('stylus'),
            styl: generateLoaders('stylus')
        }
    }

    exports.styleLoaders = function (options) {
        let output = []
        const loaders = exports.cssLoaders(options)
        for (const extension in loaders) {
            const loader = loaders[extension]
            let loaderObj = {
                test: new RegExp('\\.' + extension + '$'),
                use: loader,
            }
            if (options.cssModule) {
                loaderObj.exclude = options.cssModuleExcludePath;
            }
            output.push(loaderObj)
        }

        if (options.cssModule) {
            options.cssModule = false
            const cssModuleLoaders = exports.cssLoaders(options)
            for (const extension in cssModuleLoaders) {
                const cssModuleLoader = cssModuleLoaders[extension]
                let cssModuleLoaderObj = {
                    test: new RegExp('\\.' + extension + '$'),
                    use: cssModuleLoader,
                }
                cssModuleLoaderObj.include = options.cssModuleExcludePath;
                output.push(cssModuleLoaderObj)
            }
        }
        return output
    }
(3) 在webpack.prod.conf.js内使用

确认是否引入了utils,如果没有引入,在页面上方增加代码:

const utils=require('./utils');

修改 module内的rules属性:

    rules: utils.styleLoaders({
            sourceMap: config.build.productionSourceMap,
            extract: true,
            usePostCSS: true,
            cssModule:config.base.cssModule,
            cssModuleExcludePath:config.base.cssModuleExcludePath
    })

执行编译查看:

npm run build
(4) 在webpack.dev.conf.js内使用

确认是否引入了utils,如果没有引入,在页面上方增加代码:

const utils=require('./utils');

修改 module内的rules属性:

     rules: utils.styleLoaders({ 
            sourceMap: config.dev.cssSourceMap, 
            usePostCSS: true,
            cssModule:config.base.cssModule,
            cssModuleExcludePath:config.base.cssModuleExcludePath
    })

执行dev命令试试看

npm run dev

下一节会对 如果对编译后的文件进行gzip压缩,如何让开发环境的控制台输出更加高逼格,如何更好的对编译后的文件进行bundle分析等问题展开讨论。