webpack4生产环境和开发环境的对比

4,277 阅读10分钟

前言

近期一直在看webpack4的文档,于是给自己做了这个总结,对比一下生产环境和开发环境的区别。

开发环境

在项目开发过程中,我们关注的是能否追溯到代码的错误来源,能够及时刷新页面让我们看到代码的实际效果,因此webpack针对开发特点提供了几个插件。

source-map

webpack会将代码打包至一个文件中,一旦发生错误和警告,很难追溯到其来源,因此webpack提供了source-map,将编译后的代码映射回原始代码。

官网示例代码结果如图:

source-map有多种使用方式,官方推荐三种使用方式:devtool, SourceMapDevToolPlugin或EvalSourceMapDevToolPlugin。第一种采用的是内置插件,第二、第三种是直接插入插件。三种方式不能同时出现,否则会导致插件被置入两次。

devtool

devtool: 'inline-source-map',

webpack官网中列举出来了一些devtool属性的性能对比(地址),我们可以根据自己项目的需要来选用。

插件可以对source-map生成更细粒度的控制,可以作为插件使用,也可以在通过devtool的某些设置来自动开启

SourceMapDevToolPlugin

new webpack.SourceMapDevToolPlugin(options);

在使用TerserPlugin时,您必须使用该sourceMap选项。

EvalSourceMapDevToolPlugin

new webpack.EvalSourceMapDevToolPlugin(options);

开发工具

每次编写完代码后都需要运行npm run build非常麻烦,因此webpack提供了三种不同的开发工具支持实时更新代码。

1. webpack's Watch Mode

通过添加npm脚本的方式来监控代码的变化,在package.json中添加一行

"scripts": {
	"watch": "webpack --watch"
}

此时启动npm run watch便可以监控代码,我们可以查看到文件的hash值一直在变化,表示文件一直在跟随代码的更新而变化。

但是这个方式依旧需要开发人员手动刷新浏览器才能看到更改,因此使用者不多。

2. webpack-dev-server

该方法提供一个简单的web服务器和使用实时重新加载的能力。因此是使用较多的一个工具。webpack-dev-server从devServer中读取配置,

devServer: {
    contentBase: './dist'
}

contentBase告诉webpack-dev-server从dist目录中提供文件,显示在localhost:8080上。

webpack-dev-server并不会输出编译后的文件,而是将文件保存在内存中提供服务,开启本地服务器来监控代码并实时刷新网页。

webpack-dev-server开启的是本地服务器,而通常我们在开发过程中需要与后端进行通信,调试数据。本地服务器会有跨域问题,因此我们可以通过devServer设置代理来请求接口

devServer: {
    proxy: {
        '/api': 'http://localhost:3000'
    }
}

当然我们也可以通过一些代理软件来对处理本地请求的问题,只是webpack-dev-server自己也实现了这个功能。

更多具体的配置可以查阅官网文档

3. webpack-dev-middleware

webpack-dev-middleware是一个包装器,将webpack处理后的文件发送到服务器,在webpack-dev-server内部也是通过这个中间件来实现的,也可以作为单独的包提供,允许更多自定义设置。

webpack-dev-middleware在使用时需要配置output的publicPath属性,以确保正确提供文件。添加文件server.js, 在该文件中设置相应的自定义,便可以启动中间件运行。下面是官网的自定义示例:

生产环境

生产环境与开发环境完全不同,在生产环境中我们关注的是如何才能产生更小的代码块,压缩文件的体积,使得加载时间做到最短。

source mapping

webpack鼓励开发人员在生产环境中也加入source-map,便于调试和运行基准测试。在生产环境中应当选择构建速度较快的source-map,尽量避免在生产环境中使用inline-*** 或 eval-*** 的source-map,因为它们会显著增加包的体积并降低整体性能。

推荐配置:devtool: 'source-map'

压缩css

优化css代码是生产构建中非常重要的一环,webpack4+为此专门提供了一个css插件:MiniCssExtractPlugin,为每个包含CSS的JS文件创建一个CSS文件。它支持CSS和SourceMaps的按需加载。 与之前使用的extract-text-webpack-plugin插件(webpack4+已废弃)相比,webpack4提供的这个插件具有更加显著的优点:异步加载、没有重复编译、性能更加良好、更容易使用、定制css。而且官网指明在未来这个插件有可能会支持热重载。

需要注意的是,MiniCssExtractPlugin只应该在生产环境中被使用,而且在生产环境中应该使用它来替代style-loader。同时配合插件optimize-css-assets-webpack-plugin来压缩css文件。

module.exports = {
    optimization: {
    minimizer: [
      new TerserJSPlugin({}), // 需要显式的指定js minimalizer,因为optimization.minimizer会覆盖默认设置
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  }
}
module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader,
                "css-loader"
            ]
        }
    ]
}

Tree Shaking

tree shaking是Javascript上下文中去除死代码的一个术语,它依赖于ES5的模块语法的静态结构,即import和export语法。在webpack2中已内置支持ES2015的模块语法,在webpack4中队这一功能进行了扩充,通过package.json中的'sideEffects'来标识项目中哪些文件是纯粹的,如果未使用则可以安全修建。 但是在使用tree shaking时需要注意,只有无副作用的代码才可以应用修剪,

“副作用”定义为在导入时执行特殊行为的代码,而不是公开一个或多个导出。一个例子是polyfill,它影响全局范围,通常不提供导出。

在webpack4以上的环境中,在package.json中添加'sideEffects'属性,并将mode设置为production即可开启tree shaking。当然,tree shaking只是标识出可以被修剪掉的代码块,最后还是需要使用UglifyjsWebpackPlugin插件来进行js压缩。

注意:tree shaking只支持ES6模块语法,不能识别CommonJS语法,因为CommonJS是动态导入,无法被识别

通用功能

Code Splitting

在使用webpack的时候,我们都会学到一个概念,webpack会将所有代码都打包到一个文件中去,造成该文件的体积十分的庞大,因此webpack提供了代码拆分功能,我们可以将一些代码提取成不同的bundles,实现按需加载或并行加载。使用得当的话能很大的提升加载效率。通常有三种代码拆方式:

  • Entry Points: 通过entry的配置来手动拆分代码。
  • Prevent Duplication: 使用插件SplitChunksPlugin来删除重复代码和拆分代码。
  • Dynamic Imports: 通过模块内的内联函数调用来拆分代码。

webpack4.6.0+版本增加了对预加载和预获取的支持:

  • prefetch:将来某些导航可能需要资源
  • preload:当前导航期间可能需要资源

在webpack4+中,推荐使用SplitChunksPlugin来分离代码,这个插件允许我们将共同的依赖提取到一个现有的块或者一个全新的块中去。相较于之前使用的CommonsChunkPlugin插件(webpack4+已废弃),webpack在SplitChunksPlugin中做了更多的优化,在基于以下的条件时新的代码块会被自动创建:

  • 新的代码块被共享或者来自node_modules文件夹
  • 新的代码块大于30kb(在min+giz之前)
  • 按需加载代码块的请求数量应该<=5
  • 页面初始化时加载代码块的请求数量应该<=3

同时SplitChunksPlugin提供了丰富的api供我们实现个性化设置,我们可以根据自己的项目特性来设置如何分离代码块。详情查询

SplitChunksPlugin配置如下:

module.exports = {
        //...
        optimization: {
            splitChunks: {
                chunks: 'async',
                minSize: 30000,
                maxSize: 0,
                minChunks: 1,
                maxAsyncRequests: 5,
                maxInitialRequests: 3,
                automaticNameDelimiter: '~',
                name: true,
                cacheGroups: {
                    vendors: {
                        test: /[\\/]node_modules[\\/]/,
                        priority: -10
                    },
                    default: {
                        minChunks: 2,
                        priority: -20,
                        reuseExistingChunk: true
                    }
                }
            }
        }
};

该插件支持我们配置缓存组来缓存不同的文件,通过test配置来选择什么样的模块可以进入该缓存组。

// 该名为vendors的缓存组可以缓存所有来自node_modules的文件
splitChunks: {
    cacheGroups: {
        commons: {
            test: /[\\/]node_modules[\\/]/,
            name: "vendors",
            chunks: "all"
        }
    }
}
// 注意:这可能会导致下载额外的代码。
// 实际应用中应该只包含核心功能和框架,避免包过于庞大。

环境

webpack4中对于区分开发和生产环境的不同提出了两种解决方案:webpack-merge和env环境变量。

webpack-merge

安装了webpack-merge以后,我们需要修改原本的webpack.config.js

   - webpack.config.js
   + webpack.common.js
   + webpack.dev.js
   + webpack.prod.js

webpack.common.js是设置entry, output等通用设置和开发环境、生产环境都能使用的插件的配置文件。 webpack.dev.js需要设置mode为development,表示这是开发环境,同样我们的devtool(使用强效source-map),devServer也是配置在这个文件,我们可以在这个文件中做一些针对自己项目开发需求的个性化配置。 webpack.prod.js设置mode为production,表示生产环境,此时webpack4默认启用插件TerserPlugin,我们可以将压缩文件的插件、针对生产环境的一些优化措施放置在里面。

环境变量

webpack的环境变量跟操作系统的环境变量是不同的,webpack允许我们传入任意数量的环境变量,通过命令行--env传入。但是若要使用该环境变量,需要将module.exports变为函数,将环境变量以参数的形式传入。

// webpack.config.js
const path = require('path');

module.exports = env => {
  // 使用你的环境变量
  console.log('NODE_ENV: ', env.NODE_ENV);
  console.log('Production: ', env.production);

  return {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
};

通过这样,我们便可以区分出不同的环境需要的不同的配置。

拓展

HappyPack

webpack允许运行在node.js中,由于它是单线程模型,因此不能并行处理多个任务,Happy Pack将任务分解给多个子进程去并发执行,子进程处理完成后再将结果发送给主进程,因此能实现让webpack同时处理多个任务,减少构建时间。

const HappyPack = require('happypack');

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                // 将对.js文件的处理转交给id为babel的HappyPack的实列
                use: ['happypack/loader?id=babel'],
                exclude: path.resolve(__dirname, 'node_modules') // 排除文件
            }
        ]
    },
    plugins: [
    /****   使用HappyPack实例化    *****/
        new HappyPack({
            // 用唯一的标识符id来代表当前的HappyPack 处理一类特定的文件
            id: 'babel',
            // 处理文件,用法和Loader配置是一样的
            loaders: ['babel-loader']
        })
    ]
}

在loader中,将对文件的处理都传递给happypack/loader,利用id作为标识符。然后在plugin插件中新增HappyPack实例,告诉HappyPack需要做的事情。 HappyPack是为了解决webpack在node中单线程执行构建速度慢而存在的一个插件,能够显著的提高webpack的构建速度。

总结

在开发环境中,我们更注重的是如何及时快速的将更新的代码展示在网页上,如何快速定位错误,因此webpack提供了强效source-map和模块热更新的机制来帮助开发人员。在生产环境中,我们需要的是如何让代码包更小、构建更迅速,因此有了轻量级source-map,css代码压缩工具,tree shaking配和js压缩工具等等,都是为了让最后打包出来的代码体量更小。 同样在项目较为复杂的时候也需要将代码进行分包处理,使用异步加载,进一步提高用户使用时的加载速度,同样为了弥补webpack在node中也是单线程加载的原因,还提供了HappyPack,使得webpack能够并行加载。