sleepyShen1989 / blog

MIT License
0 stars 0 forks source link

【实战】webpack常用配置优化梳理(项目脚手架) #13

Open sleepyShen1989 opened 1 year ago

sleepyShen1989 commented 1 year ago

暂时能想到的配置都用上了

TL,DR

  1. 安装node,初始化npmnpm init -y
  2. 复制文章底部package.json部分,并安装依赖
  3. 复制文章底部postcss.config.js部分
  4. 根目录新建public目录,可以放入网页icon,页面模版
  5. 复制文章底部babel.config.js部分
  6. 复制文章底部webpack.config.(common|dev|prod).js部分
  7. 根目录新建src目录,编写js代码(引用js/css)
  8. npm run dev or npm run build

可选项

  1. 指定支持热替换的模块 (step.12)
  2. devServer的host配置0.0.0.0,让你的服务器可以被外部访问
  3. devServer的proxy配置解决代理问题
  4. 配置resolve选项的extensions/alias
  5. bundle分析:https://webpack.jakoblind.no/optimize/

  1. 安装node(运行 webpack 5 的 Node.js 最低版本是 10.13.0 (LTS))
  2. npm init -y
  3. npm install webpack webpack-cli --save-dev
    • 执行webpack命令会执行node_modules下的.bin目录下的webpack
  4. 配置package.json的script
  5. 样式支持
    1. npm install css-loader style-loader less-loader postcss-loader postcss-preset-env --save-dev
    2. 编写postcss配置:postcss.config.js
  6. 资源模块支持
    1. webpack4: raw-loader/url-loader/file-loader
    2. webpack5: asset module type
      1. asset/resource 发送一个单独的文件并导出URL
      2. asset/inline 导出一个资源的data URI
      3. asset/source 导出资源源代码
      4. asset在导出一个data URI和发送一个单独的文件之间自动选择
    3. 图片支持 asset
    4. 字体模块支持 asset/resource
  7. 生成html
    1. npm install html-webpack-plugin --save-dev
  8. 打包前清空dist目录
    1. output选项配置clean: true
  9. 新建src同级目录public,放入网页icon,页面模版
    1. HTMLWebpackPlugin配置模版路径
    2. webpack内置插件DefinePlugin,可在html模版中使用配置的变量<link rel="icon" href="<%= BASE_URL %>favicon.ico">
    3. 通过插件复制public目录中资源到dist目录 npm install copy-webpack-plugin --save-dev
  10. 配置devtool
    1. source-map: 生产环境+高质量SourceMaps
    2. eval-source-map: 开发环境+高质量SourceMaps
    3. none:生产环境最佳性能
    4. eval: 开发环境最佳性能
  11. 配置babel:babel.config.js
    1. npm install babel-loader @babel/core --save-dev
    2. npm install @babel/preset-env --save-dev
    3. 工作流程:
      1. 词法分析(代码分割)
      2. tokens数组(将分割的代码放入数组)
      3. 语法分析
      4. 生成AST树
      5. 遍历树,应用bebel内部插件
      6. 生成AST树
      7. 生成新的代码
  12. 安装webpack-dev-server
    1. npm install webpack-dev-server --save-dev
    2. 热替换:
      1. webpack-dev-server v4.0.0 开始,热模块替换是默认开启的
      2. 需要指定开启热替换的模块 module.hot.accept('./print.js', callback)
      3. 框架的HMR已经内部实现:vue-loader、react-refresh
    3. 设置代理解决跨域问题
  13. 代码分离:SplitChunksPlugin
  14. css分离: mini-css-extract-plugin
    1. npm install --save-dev mini-css-extract-plugin
  15. 优化
    1. optimization相关配置 (代码拆分,固定moduleId)
    2. babel配置include: path.resolve(__dirname, 'src'),
    3. output设置pathinfo: false
  16. 编写webpack配置
    1. webpack-merge: npm install webpack-merge --save-dev
    2. webpack.config.(common|dev|prod).js

// package.json
"scripts": {
   "dev": "webpack serve --config webpack.config.dev.js --open",
    "build": "webpack --config webpack.config.prod.js",
    "anal": "webpack --config webpack.config.prod.js --profile --json > stats.json"
},
"private": true,
"devDependencies": {
   "@babel/core": "^7.20.2",
   "@babel/preset-env": "^7.20.2",
   "babel-loader": "^9.1.0",
   "copy-webpack-plugin": "^11.0.0",
   "css-loader": "^6.7.1",
   "html-webpack-plugin": "^5.5.0",
   "less-loader": "^11.1.0",
   "mini-css-extract-plugin": "^2.6.1",
   "postcss-loader": "^7.0.1",
   "postcss-preset-env": "^7.8.2",
   "style-loader": "^3.3.1",
   "webpack": "^5.74.0",
   "webpack-cli": "^4.10.0",
   "webpack-dev-server": "^4.11.1",
   "webpack-merge": "^5.8.0"
}

// postcss.config.js
module.exports = {
   plugins: [
      require('postcss-preset-env')
   ]
}

// babel.config.js
module.exports = {
   // presets: [ ["@babel/preset-env", { 参数 } ] ]
   presets: [
      "@babel/preset-env"
   ]
}
// webpack.config.common.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin')
const { DefinePlugin } = require('webpack')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/main_[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    extensions: [".js",".json",".ts"],
    alias: {
      "@": path.resolve(__dirname, "./src")
    }
  },
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        type: 'asset',
        generator: {
          filename: 'assets/[name]_[contenthash:8][ext]'
        },
        parser: {
          dataUrlCondition: {
            // 单位字节
            maxSize: 10*1024
          }
        }
      },
      {
        test: /\.(eot|ttf|woff2?)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[name]_[contenthash:8][ext]'
        },
      },
      {
        test: /\.js$/i,
        include: path.resolve(__dirname, 'src'),
        use: {
          loader: 'babel-loader',
        }
      },
    ],
  },
  plugins: [
    // 插件执行顺序由插件内部的实现决定
    new HTMLWebpackPlugin({
      // 按需配置是否设置html模版
      template: './public/index.html'
    }),
    // 配置模版中的变量
    new DefinePlugin({
      BASE_URL: "'./'"
    })
  ]
};

// webpack.config.dev.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common')

module.exports = merge(commonConfig, {
  mode: 'development',
  devtool: 'eval-source-map',
  devServer: {
    // 模版中使用的当前目录(./)所对应的目录
    // 某些资源开发阶段不复制(CopyWebpackPlugin拷贝资源对性能的消耗)
    // 生产阶段才复制 使用static指定目录
    // 默认是 'public' 文件夹,可以用数组来监听多个静态资源目录
    // 建议使用绝对路径
    // static: {
    //   directory: path.join(__dirname, 'public'),
    // },

    // 想让你的服务器可以被外部访问,像这样指定
    // host:'0.0.0.0',

    // proxy: {
    //   "/api": "http://localhost:8080", // /api/user -> http://localhost:8080/api/user
    //   "/api": {
    //     target: "http://localhost:8080",
    //     pathRewrite: { '^/api': '' },  // /api/user -> http://localhost:8080/user
    //   }
    // }
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          'style-loader', 
          'css-loader',
          'postcss-loader'
        ],
      },
      {
        test: /\.less$/i,
        use: [
          'style-loader', 
          'css-loader',
          'postcss-loader',
          'less-loader'],
      },
    ],
  },
});

// webpack.config.prod.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common')

module.exports = merge(commonConfig, {
  mode: 'production',
  devtool: 'source-map',
  output: {
    clean: true,
    // 取消输出的 bundle 中生成路径信息(性能)
    pathinfo: false,
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader'
        ],
      },
      {
        test: /\.less$/i,
        use: [
          MiniCssExtractPlugin.loader, 
          'css-loader',
          'postcss-loader',
          'less-loader'],
      },
    ],
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: [
              '**/index.html'
            ]
          }
        }
      ]
    }),
    new MiniCssExtractPlugin()
  ],
  optimization: {
    // **确保** webpack 编译生成的contenthash一致

    // 模块标识符
    // 默认情况下,当模块解析顺序发生变化,module.id也会随之改变
    moduleIds: 'deterministic',

    // 提取引导模版
    // webpack 在入口 chunk 中,包含了某些 boilerplate(引导模板)
    // 多次构建(在某些版本下)可能会因为引导模版的改变导致contenthash的改变
    // 将 runtime 代码拆分为一个单独的 chunk
    runtimeChunk: 'single',
    // 将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    }
  },
});