05-01-webpack

js 痛点

  • 文件依赖度高;
  • 命名冲突/变量污染;
  • 可读性差;

模块化

  • 把复杂问题分解成相对独立的模块,这样的设计可以降低程序复杂性,提高代码的重用,也有利于团队协作开发与后期的维护和扩展;

模块化规范

node:cjs-commonjs
  • 有一个全局对象:module;
  • 导出一:通过 module.exports;
  • 导出二:通过 exports.a = function() {};
  • 引入:require(path),const a = require('./a.js');
  • 优点:
    • 拥有独立作用域;
    • 依赖关系处理简单;
浏览器:amd,异步模块定义
  • 定义模块:define(id?, depend?, factory)
  • 工厂函数会提供三个参数:
  • require
  • exports
cmd,类似于 amd
umd:处理各种模块化之间的差异
es6:esm-ECMAScript module
  • 使用:

  • 导出一:export default 模块,默认导出;

  • 引入一:import 模块名 from 模块路径,默认导入;

  • 要求必须在服务器环境下,才能运行

  • 导出二:export 模块;,多导出;

  • 引入二:import {a as A} from './a.js';,模块名必须等同于导出模块名,可以通过 as 重命名;

  • esm,IE8、IE9 不支持,这是引入了 webpack,模块打包器。

webpack

  • webpack:核心代码;
  • webpack-cli:cli 命令行界面;
正常目录解读
  • src:js 代码,逻辑代码;
  • public:html 存放目录;
使用
  • webpack 只认识相对路径;
没有配置时
  • 可以在控制台,执行 node_modules/.bin/webpack ./src/index.js
自定义配置
  • 创建 webpack.config.js;

  • webpack 会运行到 node 环境下面,所有使用module.exports = {};

  • entry:入口文件;

  • output:输出目录,是一个对象;,里面路径接受绝对路径;

    // 一对一
    const { resolve } = require("path");
    
    module.exports = {
      // 入口文件
      entry: "./src/index.js",
    
      // 输出目录
      output: {
        // 接受绝对路径
        path: resolve(__dirname, "build"),
        filename: "index.js",
      },
    };
    
    // 多对一
    const { resolve } = require("path");
    
    module.exports = {
      // 入口文件
      entry: [
        './src/index.js',
        './src/b.js'
      ],
    
      // 输出目录
      output: {
        // 接受绝对路径
        path: resolve(__dirname, "build"),
        filename: "index.js",
      },
    };
    
    // 多对多
    entry: {
      index: "./src/index.js",
      b: "./src/b.js"
    },
    
    // 输出目录
    output: {
      // 接受绝对路径
      path: resolve(__dirname, "dist"),
      filename: "[name].js",
      clean: true,
    },
    
  • mode:production、development、none

    • 默认 production;

webpack 会将任意文件数据作为模块进行处理;

当 webpack 遇到不能解析的模块时,webpack 会找到 module 对象下面的 rules,去匹配对应规则;如果有对应规则匹配时,我们就使用对应的 loader 去解析;

  • module

    module: {
      rules: [
        {
          test: /\.txt$/,
          use: "raw-loader",
        },
        {
          test: /\.(png|gif|jpe?g)$/,
          use: {
            loader: "file-loader",
            options: {
              // 图片打包后文件名
              name: "[name]_[contenthash].[ext]",
              // 打包后存放目录,相对于打包文件夹 dist
              outputPath: './images',  // 使用时,获取的是绝对路径
              // 打包后存放目录,相对于 dist 文件
              publicPath: "./images", // 使用时,引用的是相对路径 ./dist/images/redux_xxx.jpg
    
              // url-loader,多一个 limit,number 类型;
              // 当图片小于此值时,url-loader 不会对文件进行打包,而是转为 base64 格式,引入到 js 文件中
              // limit: 10240,
            },
          },
        },
        {
          test: /\.css$/,
          // loader 执行顺序:从下至上,从右至左
          use: [
            "style-loader",
            {
              loader: "css-loader",
              options: {
                // 启用 url
                url: true,
                // 启用 @import
                import: true,
                // 启用 sourcemap
                sourceMap: false,
              },
            },
          ],
        },
      ],
    }
    
    // 自定义 loader
    module.exports = {
      // ...
      // 配置 loader 解析路径,当自定义 loaders 时,需要使用
      // 如在根目录下创建 loaders 来存放自定义 loader
      resolveLoader: {
        modules: ["node_modules", resolve(__dirname, "./loaders")],
      },
      module: {
        rules: [
          {
            test: /\.doc$/,
            use: "doc-loader",
          },
        ],
      },
    }
    
    const MarkDown = require('markdown-it');
    
    const md = new MarkDown(); 
    
    module.exports = (source) => {
      console.log('source: ', md.render(source));
    
      // return a Buffer or String,返回的结果会直接 eval
      return `export default \`${md.render(source)}\``;
    }
    
    • webpack5 之后,loader 变更
    - asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
    - asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
    - asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
    
  • plugin:都是以类或者构造函数的形式出现

    • clean-webpack-plugin
      // CleanWebpackPlugin
      // webpack4.0
      const { CleanWebpackPlugin } = require("clean-webpack-plugin");
      // ...
      plugins: [
        new CleanWebpackPlugin(),
      ]
      
      // webpack5.0
      output: {
        // 接受绝对路径
        path: resolve(__dirname, "dist"),
        filename: "main.js",
        clean: true,
      },
      
    • html-webpack-plugin:自动生成一个 html 文件,与打包后的 js 文件相关联
    • mini-css-extract-plugin:
      const MiniCssExtractPlugin = require("mini-css-extract-plugin");
      // ...
      module: {
        rules: [
          {
            test: /\.css$/,
            // loader 执行顺序:从下至上,从右至左
            use: [
              // "style-loader", // 将 css 样式添加到 head 当中的 style 里面;如果样式很多时,导致html文件过大,同时也不能对 css 进行优化,这时可以用 mini-c's's
              MiniCssExtractPlugin.loader,
              "css-loader", // 处理 css 文件
            ],
          },
          {
            test: /\.doc$/,
            use: "doc-loader",
          },
        ],
      },
      
      // ...
      plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].css'
        }),
      ],
      
  • devServer

    devServer: {
      port: 9000,
      open: true, // 自动开启浏览器
      proxy: {
        // 请求地址:服务器地址
        // '/api' : 'http://localhost:3000'
        'api': {
          target: 'http://localhost:3000',
          pathRewrite: {'^/api': ''},
          secure: false,
        }
      },
      hot: true,  // 默认开启
    }
    
  • devtool:开发模式下,方便调试用

    // source-map,要严格遵循下列顺序:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
    devtool: 'eval-cheap-source-map',
    
webpack-dev-server
  • webpack4.0:在 package.json 中添加执行命令 dev: webpack-dev-server
  • webpack5.0:在 package.json 中添加执行命令 dev: webpack serve
  • 启动开发服务器,打包后生成的文件,会存到内存中,不会写入硬盘,这样可以提高开发效率

代码分割

1.最基本的代码分割
  // 多对多打包方式,可能会导致同一模块打包进多个 chunk
  entry: {
    index: {
      import: "./src/index.js",
      dependOn: ["axios", "a"]
    },
    b: {
      import: "./src/b.js",
      dependOn: ["axios", "a"]
    },
    axios: "axios",
    a: "./src/a.js"
  },
  // 当你选择使用代码分割时,webpack可能会受[启发式]的在多个chunk中复制依赖模块。如果发生了这种事情,这时您有可能得到相同模块的多个实例,他们之间的状态将会很难保证同步。当然,您可以通过设置 `optimization.runtimeChunk` 为 `“single”` 进行解决。这并不能阻止Webpack在入口点之间复制模块代码,但它可以防止Webpack在运行时创建同一模块的两个实例。
  optimization: {
    // 配置 runtimeChunk 为 single,webpack 会帮助生成一个 runtime chunk,然后他会把加载的包移到 runtime 中
    runtimeChunk: "single"
  },
2.SplitChunksPlugin
  • CommonsChunkPlugin 曾被用来避免他们之间的重复依赖,但是不可能再做进一步的优化。
  • 从 webpack v4 开始,移除了 CommonsChunkPlugin,取而代之的是 optimization.splitChunks。
entry: {
  index: "./src/index.js",
  b: "./src/b.js",
},
optimization: {
  splitChunks: {
    // async 表示只从异步加载模块中进行拆分
    // initial 表示只从入口模块进行拆分
    chunks: 'all'
  }
},
  • 默认配置
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
动态导入
// 异步加载 按需加载
// 使用 import() 函数时,会返回一个 promise 对象
// 内联注释 magic comments,需要按照 json 格式编写
import( 
  /* webpackChunkName: "my-chunk-name" */
  'axios'
).then(res => {
  console.log('axios: ', res);
})

预加载 / 预获取

  • 其实是针对于动态导入资源加载优化
  • preload:预加载,当我们配置时,被配置的模块会在父 chunk 加载时并行加载该模块;
  • prefetch:预获取,当我们配置时,被配置的模块会在浏览器闲置时,也就是未来某个时刻下载模块
import( 
  /* webpackChunkName: "my-chunk-name" */
  /* webpackPrefetch: true */
  /* webpackPreload: true */
  'axios'
).then(res => {
  console.log('axios: ', res);
})

外部扩展

  • 防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
  • key:是我们需要进行外部扩展的包名,后续打包时不会将该包打包;
  • value:是该库声明的全局变量;
module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

tree shaking

  • 将上下文中的dead-code移除,就像摇树上的枯叶使其掉落一样;
optimization: {
  usedExports: true,
},  
mode: 'production',

webpack.config.js

const { resolve } = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // 最基本的代码分割
  // 多对多打包方式,可能会导致同一模块打包进多个 chunk
  entry: {
    index: "./src/index.js",
    b: "./src/b.js",
  },
  // 当你选择使用代码分割时,webpack可能会受[启发式]的在多个chunk中复制依赖模块。如果发生了这种事情,这时您有可能得到相同模块的多个实例,他们之间的状态将会很难保证同步。当然,您可以通过设置 `optimization.runtimeChunk` 为 `“single”` 进行解决。这并不能阻止Webpack在入口点之间复制模块代码,但它可以防止Webpack在运行时创建同一模块的两个实例。
  // optimization: {
  //   // runtimeChunk: "single"
  //   splitChunks: {
  //     // async 表示只从异步加载模块中进行拆分
  //     // initial 表示只从入口模块进行拆分
  //     chunks: 'all'
  //   }
  // },

  // 输出目录
  output: {
    // 接受绝对路径
    path: resolve(__dirname, "dist"),
    filename: "[name].js",
    clean: true,
  },

  mode: "development",

  // 配置 loader 解析路径,当自定义 loaders 时,需要使用
  // 在根目录下创建 loaders 来存放自定义 loader
  resolveLoader: {
    modules: ["node_modules", resolve(__dirname, "./loaders")],
  },

  // loader 使用
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: "raw-loader",
      },
      {
        test: /\.(png|gif|jpe?g)$/,
        use: {
          loader: "file-loader",
          options: {
            // 图片打包后文件名
            name: "[name]_[contenthash].[ext]",
            // 打包后存放目录,相对于打包文件夹 dist
            outputPath: "./images", // 使用时,获取的是绝对路径
            // 打包后引用目录
            publicPath: "./images", // 使用时,引用的是相对路径 ./images/redux_xxx.jpg

            // url-loader,多一个 limit,number 类型;
            // 当图片小于此值时,url-loader 不会对文件进行打包,而是转为 base64 格式,引入到 js 文件中
            // limit: 10240,
          },
        },
      },
      {
        test: /\.css$/,
        // loader 执行顺序:从下至上,从右至左
        use: [
          // "style-loader", // 将 css 样式添加到 head 当中的 style 里面;如果样式很多时,导致html文件过大,同时也不能对 css 进行优化,这时可以用 mini-c's's
          MiniCssExtractPlugin.loader,
          "css-loader", // 处理 css 文件
        ],
      },
      {
        test: /\.doc$/,
        use: "doc-loader",
      },
    ],
  },

  // 注册插件的位置,plugins 的使用
  // webpack 会自动调用对应插件的 apply 方法,把插件注册到 webpack 对应的生命周期当中
  // 插件 是 webpack 的 支柱 功能。Webpack 自身也是构建于你在 webpack 配置中用到的 相同的插件系统 之上
  // 插件目的在于解决 loader 无法实现的其他事。Webpack 提供很多开箱即用的 插件。
  plugins: [
    // new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: "app.html", // 会自动创建一个纯净的 app.html 文件
      template: "./public/index.html", // 会把已有 html 文件中的信息带上
      title: "app", // <%=htmlWebpackPlugin.options.title%>
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),
  ],

  devServer: {
    port: 9000,
    open: true, // 自动开启浏览器
    proxy: {
      // 请求地址:服务器地址
      // '/api' : 'http://localhost:3000'
      'api': {
        target: 'http://localhost:3000',
        pathRewrite: {'^/api': ''},
        secure: false,
      }
    },
    hot: true,  // 默认开启
  },

  // source-map,要严格遵循下列顺序:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
  devtool: 'eval-cheap-source-map',
};

你可能感兴趣的:(05-01-webpack)