重学webpack(生产环境的配置)

873 阅读9分钟

webpack生产环境的总配置

提取css成单独文件

  • 在webpack开发环境下我们往往不会让源目录中css文件在打包后单独生成一个css文件,但是这往往会使得打包后包含这些css文件内容的js文件体积臃肿,甚至可能出现闪屏现象,因为因为它的工作原理时是先加载js文件再去加载css文件
  • 场景需求:在webpack生产环境下我们往往需要让源目录中css文件在打包后单独生成一个css文件
  • 之前通过'css-loader'和'style-loader'将css代码统一整合进自动给构建出的js文件中,通过自动创建<style></style>的方式自动引入css样式,现在我们需要单独的生成css文件
  • 我们可以使用配合插件'mini-css-extract-plugin'来完成我们的需求,这可以让源目录中css文件在打包后单独生成一个css文件(并且保持输出后的目录结构与源目录结构一致),并且让构建后目录中的build/index.html通过自动创建<link href="css/built.css" rel="stylesheet">的方式引入css样式。
  • 构建后目录中的build/index.html通过自动创建<link href="css/built.css" rel="stylesheet">的方式引入css样式,这样子就不会出现闪屏现象了
  • 该构造目录的结构为:
  • 执行webpack命令后自动创建的build/index.html文件的代码内容(注意css样式和je样式是自动引入的):
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
<link href="css/built.css" rel="stylesheet"></head>
<body>
  <div id="box1"></div>
  <div id="box2"></div>
<script type="text/javascript" src="js/built.js"></script></body>
</html>
  • webpack.config.js的配置代码如下:
    • 使用'mini-css-extract-plugin'插件并使用 MiniCssExtractPlugin.loader这个来取代原先的'style-loade'
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader'的作用是创建style标签,将样式放入
          // 下面这个loader取代style-loader。
          // 下面这个loader的作用:提取build/js/built.js中的css内容成单独文件
          MiniCssExtractPlugin.loader,
          // 将css文件整合到js文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      // 对输出的css文件进行重命名
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};
  • 总结提取css成单独文件的方式:

css的兼容性处理

  • 场景需求:我们在书写CSS页面时往往会遇到书写的的代码在一些浏览器下不兼容的问题,这会导致页面的样式加载不出来,因此我们在生产环境下需要对css做相应的兼容性处理
  • 解决方案之一:我们在这里使用postcss来处理css的兼容性问题。在webpack下想要使用postcss的功能需要借助使用'postcss-loader'这个Loader和这个Loader的插件'postcss-preset-env'来实现css的兼容性处理。
  • 第一步:下载'postcss-loader''postcss-preset-env'即(cnpm i postcss-loader postcss-preset-env -D)
  • 注意点:Loader的两种写法:一种是使用Loader的默认配置写法(不修改其任何配置),一种是使用自定义配置写法(修改其相关配置)
  • 默认配置写法举例代码说明:
 module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'css-loader'
        ]
      }
    ]
}
  • 自定义配置写法举例代码说明:
module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        //使用 'css-loader'的默认配置写法
        'css-loader',

        // 使用对象的写法来修改loader的配置
        {
          loader: 'postcss-loader',
          options: {
            ident: 'postcss',
            plugins: () => [
              // 使用postcss的插件
              require('postcss-preset-env')()
            ]
          }
        }
      ]
    }
  ]
}
  • 注意点'postcss-preset-env'可以识别具体的环境从而加载指定的配置使得css精确的兼容到指定的浏览器的版本
  • 注意点'postcss-preset-env'可以帮postcss找到package.jsonbrowserslist里面的配置,然后通过这个'browserslist'里面的配置加载指定的css兼容性样式
  • 第二步:在package.json中去配置browserslist告知如何兼容css样式,书写的格式可以参考下面的写法(更多的配置可以在github上搜索关键字"browserslist"):
"browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  • 注意点:"development"是开发环境,而"production"是生产环境
  • 注意点:在第二步中运行命令webpack默认是看生产环境,即在package.json中配置的"production"会默认生效。注意这里是默认看生产环境(即"production")的配置,而不是去看webpack.config.js的mode配置,也就是说与webpack.config.js的mode配置是无关的
  • 注意点:如果我们想要让css兼容规则以开发环境的配置来做(即以package.json中配置的"development"的规则来兼容),需要单独设置nodejs的环境变量(即在webpack.config.js中添加process.env.NODE_ENV = 'development')
  • 该构造目录的结构为:
  • webpack.config.js的配置代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 设置nodejs环境变量让css兼容规则以开发环境的配置来做
// process.env.NODE_ENV = 'development';

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          //使用 'css-loader'的默认配置
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                // 使用postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};

  • 总结css的兼容性处理的方式:

压缩css文件

  • 场景需求:压缩css文件
  • 实现方式之一:引入压缩css的插件'optimize-css-assets-webpack-plugin'。(即cnpm i optimize-css-assets-webpack-plugin -D)并使用,执行webpack命令后即见效果。
  • webpack.config.js的配置代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

//引入压缩css的插件'optimize-css-assets-webpack-plugin'
//cnpm i optimize-css-assets-webpack-plugin -D
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    // 压缩css
    new OptimizeCssAssetsWebpackPlugin()
  ],
  mode: 'development'
};
  • 总结压缩css的方式:

js语法检查之eslint

  • 场景需求:我们往往需要统一代码书写规范,例如检查出一些语法错误、格式不规范等问题并自动帮我们修改大部分的错误
  • 实现:语法检查最常用的工具是eslint。在webpack中如果想要使用eslint工具,需要下载eslint-loader 和eslint这两个依赖即(cnpm i eslint-loader eslint -D)
  • 注意点:我们只想检查自己写的源代码,并且相对来说第三方的库(例如node_modules下的文件)是不用检查的,所以我们需要设置eslint的检查规则。即我们需要在在package.json中设置'eslintConfig',规定好eslint检查规则。书写的格式可以参考下面的写法:
  • 注意点:在这里我们使用的eslint检查规则是airbnb风格指南(语言规范),也可以使用其他的检查规则,使用airbnb风格指南(语言规范)需要再引入三个依赖即(eslint-config-airbnb-base、eslint-plugin-import、eslint),所以需要下载cnpm i eslint-config-airbnb-base、eslint-plugin-import、eslint -D
"eslintConfig": {
  "extends": "airbnb-base",
  "env": {
    "browser": true
  }
},
  • webpack.config.js的配置代码如下:
const { resolve } = require('path');

//引入插件'html-webpack-plugin'
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    // 打包后自动创建build/js/built.js
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader',
        options: {
          // 自动修复eslint的错误
          fix: true
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 以谁为模板
      // 复制 './src/index.html' 文件
      // 并自动引入 打包输出的所有资源(JS/CSS/IMG/iconFont)
      template: './src/index.html'
    })
  ],
  //这里是开发模式的原因是好观察
  mode: 'development'
};
  • 注意点:如果我们再某个JS文件想要让某行代码忽略eslint的语法检测,我们可以在紧挨着该代码的上一行写eslint-disable-next-line,书写的格式可以参考下面的写法:
// 下一行eslint所有规则都失效(下一行不进行eslint检查)
// eslint-disable-next-line
console.log(add(2, 5));
  • 该构建目录的结构为:
  • 总结js语法检查之eslint配置的方式:

js兼容性处理之babel

  • 场景需求:当我们使用ES6、ES7等较为高级的语法来书写js代码时,执行webpack命令打包后默认是不会改变原JS代码的(默认不会做兼容性处理)。在这种情况下,如果该JS代码运行在低版本浏览器时,可能会出现报错等问题。所以我们需要让webpack打包时记得做js的兼容性处理
  • 解决方式之一js兼容性处理可以使用babel。在webpack中如果想要使用babel工具,需要下载babel-loader和@babel/core即(cnpm i babel-loader和@babel/core -D)
  • 解决方式之一的注意点:在配合 @babel/preset-env(即cnpm i @babel/preset-env -D)可以对基本的js语法进行兼容性处理(例如箭头函数打包后变为普通函数、const打包后变为var等等)。它的问题是只能转换基本语法,但是如promise等高级语法还是不能转换。
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets:['@babel/preset-env']
        }
      }
    ]
  },
  • 解决方式之二:由于解决方式一中在配合 @babel/preset-env使用的情况下依旧没有办法解决像promise等高级语法的转换,但是我们还是有这方面的js兼容性处理需求时可以使用@babel/polyfill 即(cnpm i @babel/polyfill -D),
  • 解决方式之二的注意点:使用方式是在下载@babel/polyfill之后,在入口文件(这里是src/js/index.js)中引入该文件即可(即import '@babel/polyfill';)
  • 该构造目录为:
  • 解决方式之二的注意点:这个@babel/polyfill 可以解决全部的js兼容性处理,但是它的问题是将所有兼容性代码全部引入,体积太大了
  • 下面是src/js/index.js的代码:(将所有的兼容性代码引入入口文件并一起打包出去,使用此方式无需在webpack.config.js配置其他选项,直接在入口文件引入'@babel/polyfill'文件即可)
import '@babel/polyfill';

const add = (x, y) => {
  return x + y;
};

console.log(add(2, 5));

const promise = new Promise(resolve => {
  setTimeout(() => {
    console.log('定时器执行完了~');
    resolve();
  }, 1000);
});

console.log(promise);
  • 解决方式三使用core-js配置做到按需加载(即在有需要做兼容性处理的js代码就做兼容性处理),我们需要先下载core-js即(cnpm i core-js -D),然后去配置core-js,最后执行webpack命令即可看见效果。
  • 解决方式三的注意点:在使用解决方式三core-js不能够和解决方式二(即在入口文件中import '@babel/polyfill')共用,需要删除入口文件中的这一行(即import '@babel/polyfill')代码。
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎么样的兼容性处理
          // 告诉'babel-loader'要进行怎么样的语法转换
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
  • 总结js兼容性处理配置的方式:(推荐使用按需加载来实现js兼容性处理)

压缩html和js文件

  • 生产环境下会自动压缩js代码(即在webpack.config.js中配置mode: 'production')
  • 我们可以观察到在webpack.config.js中配置mode: 'production'后会启用UglifyJsPlugin插件,该插件会去压缩JS代码。
选项 描述 特点
development 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin 能让代码本地调试 运行的环境
production 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin和UglifyJsPlugin 能让代码优化上线 运行的环境

  • 压缩JS代码的webpack.config.js的配置代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  // 生产环境下会自动压缩js代码
  mode: 'production'
};
  • 压缩HTML文件(记住HTML的标签是做不了兼容性处理的),可以使用HtmlWebpackPlugin插件来压缩HTML文件。
  • 之前我们用HtmlWebpackPlugin插件来创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS),现在我们还可以用它来帮我们压缩HTML文件。
  • 压缩HTML文件的webpack.config.js的配置代码如下:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 压缩html代码
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ],
  mode: 'production'
}
  • 总结压缩html和js文件配置的方式:

综合案例:生产环境的基本总配置

  • 需求一:提取css成单独文件
  • 需求二:css的兼容性处理
  • 需求三: 压缩css文件
  • 需求四:js语法检查之eslint
  • 需求五:js兼容性处理之babel
  • 需求六:压缩html和js文件
    • 注意点:注意路径的引入问题:在不同的目录结构中要使用正确的路径引用对应的资源
    • 注意点:有多个Loader对同一类型的文件进行处理时一定要注意顺序!!!,例如在处理.less文件时webpack.config.js的代码如下:(此时打包后的css文件是单独的文件并自动通过<link>的方式被引入HTML页面)
// 为了复用loader的分离loader写法
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中配置'browserslist'
    // css兼容规则默认以生产环境的配置来做
    // 如果需要以开发环境的配置来做需单独设置nodejs的环境变量
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];


module: {
  rules: [
     {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        /**
         * 注意顺序!!!!!!
         * 顺序是使用'less-loader'先转为.css文件
         * 在执行'postcss-loader'做css的兼容性处理
         * 在执行'css-loader'将css代码写入打包后自动构建的js文件中
         * 再去执行MiniCssExtractPlugin.loader
         * 将css样式以link标签的方式引入页面
         */
        use: [...commonCssLoader, 'less-loader']
      },
      ]
    }
  ]
},
    • 注意点:当js兼容性处理之babel和js语法检查之eslint遇到一起时,它们都要对js文件进行处理时,我们要先先执行eslint语法检查在执行babel兼容性处理。可以在webpack.config.js的Loader配置中添加 enforce: 'pre'确保优先执行
    • 注意点:js语法检查之eslint需要我们在package.json中设置'eslintConfig',规定好eslint检查规则。即:
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
    • 注意点:在处理css文件的兼容性处理中执行webpack命令默认是看生产环境,即在package.json中配置的"production"规则会默认生效。如果我们想要让css兼容规则以开发环境的配置来做,需要单独设置nodejs的环境变量(即在webpack.config.js中添加process.env.NODE_ENV = 'development')
"browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  • 此时的webpack.config.js的总配置为:
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

//压缩css代码
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

//创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS),压缩HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');


// 定义nodejs环境变量:决定使用package.json中"browserslist"的哪个环境
// 在处理css文件的兼容性处理中执行`webpack`命令默认是看生产环境("production")
// 如果想要让css兼容规则以开发环境的配置来做需要做如下配置:
process.env.NODE_ENV = 'production';



// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  //入口文件
  entry: './src/js/index.js',
  //输出目录路径
  output: {
    filename: 'js/built.js',
    //需要引入 const { resolve } = require('path');
    //打包构建后的目录结构要与源目录结构保持一致
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },

      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 确保优先执行,不在考虑书写的先后顺序
        enforce: 'pre',
        //使用eslint需要在package.json中配置'eslintConfig'
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      //重命名文件路径及名称与源目录结构保持一致
      filename: 'css/built.css'
    }),

    //只需要如此调用就可以压缩css代码无需再多余操作
    new OptimizeCssAssetsWebpackPlugin(),


    new HtmlWebpackPlugin({
      template: './src/index.html',
      //压缩html代码
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  //生产环境自动压缩js代码
  mode: 'production'
};
  • 总结生产环境配置的方式:
  • 该项目的package.json代码为:
{
  "name": "webpack_code",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/polyfill": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel": "^6.23.0",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "css-loader": "^3.4.2",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.0.0",
    "eslint-loader": "^3.0.3",
    "eslint-plugin-import": "^2.20.1",
    "file-loader": "^5.0.2",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^1.1.3",
    "terser-webpack-plugin": "^2.3.5",
    "thread-loader": "^2.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3",
    "workbox-webpack-plugin": "^5.0.0"
  },
  "dependencies": {
    "jquery": "^3.4.1"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css"
  ]
}