webpack

121 阅读7分钟

webpack相关文档

webpack-cli文档

cli指令文档

webpack官网

webpack了解

npm i webpack webpack-cli -D

npm i webpack-dev-server -D

假设执行webpack指令 webpack --config wb.config.js 是依赖webpack-cli来解析执行的 其实vue和react都有自己的cli,所以不需要使用webpack的cli

webpack --> node_modules/.bin下webpack --> 依赖webpack-cli runCli函数参数为webpack的config --> 依赖webpack的config打包

plugin

用于打包优化、资源管理、环境变量注入 例如:webpack提供的DefinePlugn插件

const { DefinePlugin } = require('webpack')
module.exports = {
  entry: './src/index.js',
  plugins: [
    new DefinePlugin({ // 字符串内容会被当成代码来解析的
      uname: "'sky'",
      age: '18' // number 18
    })
  ]
}

loader

例如:npm i ts-loader -D 必须要有一个配置文件tsconfig.json,可以通过tsc --init 指令创建,执行tsc指令需要全局安装ts,npm i typescript -g

  • webpack只能理解js和json文件
  • 使用loader可以让webpack去处理其他类型文件,安装对应文件的loader插件在rules配置后转为有效(js)模块,以及被添加到依赖图中
module.exports = {
  entry: "./src/index.js",
  module: {
     rules: [
      {
        test: /.css$/,
        use: [ // 多个loader使用use 需额外配置为useEntry对象,不需要配置则字符串
          style-loader
          {
            loader: 'css-loader',
            options: {
              // css文件内使用@import导入的css文件,会重新执行postcss-loader解析
              // 如果不写这个属性则不会再次使用postcss-loader解析,1 = css-loader后面剩余loader的length
              importLoaders: 1 
            }
          },
          // "postcss-loader" 使用postcss.config.js只写这个就行了
          { 
            // postcss-preset-env 对css、less、scss 需要分别配置,所以可以选择创建一个postcss.config.js的文件
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env"
                ]
              }
            }
          }
        ],
        // loader: 'css-loader'  单个loader + 配置
      },
    ],
  }
};

postcss.config.js

module.exports = {
  plugins: [
    "postcss-preset-env"
  ],
};

自定义loader

这个文件可以参考看看自定义loader相关的

  1. Loader本质上是一个导出为函数的JavaScript模块,这个函数会接收三个参数
  • content:资源文件的内容
  • map:sourcemap相关的数据
  • meta:一些元数据
  1. loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去
  2. 多个Loader使用,它的执行顺序是从后向前、从右向左的
  3. webpack在打包过程中会按照规则顺序调用处理某种文件的 loader ,然后将上一个 loader 产生的结果或者资源文件传入进去,当前 loader 处理完成后再交给下一个 loader
  4. 还一个pitch-loader

loader的执行顺序是相反的:

  1. run-loader先优先执行PitchLoader,在执行PitchLoader时进行loaderIndex++
  2. run-loader之后会执行NormalLoader,在执行NormalLoader时进行loaderIndex--

如何改变它们的执行顺序:使用enforce

const schema = {
  type: 'object', //options是一个对象
  properties: {
      //haha是一个number类型
      haha: {
          type: 'string'
      },
  }
}
module.exports = function (content, map, meta) {
  // 获取关于loader的options参数, 校验loaderOptions参数 schema为一个json
  const options = this.getOptions(schema);
  console.log(options);

  // 第二种同步 返回结果 (1)错误 (2)文本内容
  // this.callback(null, content)

  // 第一种同步 返回content
  // return content

  // 异步返回结果
  const callback = this.async()
  // 模拟网络请求
  setTimeout(() => {
    callback(null, content)
    console.log('net done');
  }, 1000)

}

module.exports.patch = function (content, map, meta) {
  console.log('patch loader sky');
}

// 执行raw loader 处理图片、字体等文件  这时候content为Buffer类型的,而非字符串了
module.exports.raw = true
module.exports = {
  output: {
    path: resolve('build'),
  },
  resolveLoader: {
    modules: ["./loader", "node_modules"]
  },
  module: {
    rules: [
      {
        test: /\.js$/i,
        use: [{
          // "./loader/sky-loader" 传入的路径和context是有关系的
          loader: "sky-loader.js",
          options: {
            haha: 1
          }
        }],
        // 默认所有的loader都是normal,使用enforce改变loader的执行顺序
        enforce: "pre"
      }
    ]
  }
}

mode (production 默认值 | development)

  1. 会影响process.env.NODE_ENV值的变化 或 cli修改 webpack --mode=development
  2. mode的值不同会影响webpack的默认配置
生产环境
  • 需要压缩html/css/js代码/图片
  • 可能需要分离CSS成单独的文件,以便多个页面共享一个CSS文件
开发环境
  • 需要生产sourcemap
  • 需要打印debug信息
  • 需要live reload 和 hot reload的功能

devServer

npm i webpack-dev-server

  1. 会启动一个http开发服务器,把一个文件夹作为静态根目录
  2. 为了提高性能,使用的内存文件系统(memory-fs
  3. 默认情况下devServer会读取打包后的路径(output的path配置路径),如果没有找到对应文件则会查找到devServer的static配置的路径
  4. 可以理解为静态文件根目录可以是有多个的
devServer的publicPath

作用:指定本地服务所在的文件夹,建议跟output的publicPath保持一致吧

output的publicPath属性

作用:指定index.html文件打包引用的一个基本路径

  1. 默认值是一个空字符串,所以打包后引入js文件时,路径是 bundle.js
  2. 开发中,可以将其设置为 / ,路径是 /bundle.js,那么浏览器会根据所在的域名+路径去请求对应的资源
  3. 打包文件插入到index.html里src编写方式:域名 + publicPath + filename = ‘http://127.0.0.1:5500/’ + '/’ + ‘js/sky.js‘
HMR(热更新)
  1. 默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可
  2. 在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading

原理: 没啥用,随便看看得了

  1. webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket)
  2. express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    
    hot: true,
    compress: true, // 打包后文件gzip压缩,浏览器在解压 哈哈哈内存里哦
  },
}

开启热更新,发生变化的代码模块才去更新,不会刷新整个页面,设置后会发现依然刷新整个页面,因为还需要手动设置

import "./pages/home"
console.log(111, uname, age);
const getName = () => 'sksksk';
getName()
console.log(getName());

console.log(module.hot, 'module');
// 了解了解得了,框架帮忙做hmr咯,例如:vue-loader、react-refresh
if(module.hot) { 
  module.hot.accept('./pages/home', () => {
    console.log('home更新了');
  })
}

devtool 决定打包文件的错误提示信息

可选参数

  1. cheap:报错只有行信息,不包含列
  2. inline:生成xxx.map.js文件,也会生成source-map文件但是以DataUrl的形式添加到bundle(打包)文件的后面
  3. eval:默认不生成sourcemap,在eval内执行代码,如果有sourcemap则将sourcemap以字符串的形式添加到eval函数中
  4. hidden:生成sourcemap文件,但不会对其引用
  5. nosources:也会生成sourcemap文件,但是报错点进去没提示,不会生成源代码文件

source-map:生成xxx.map.js文件

Babel 之 我是一个编译器!嘿嘿

npm i babel-loader @bable/cli @babel/core @babel/preset-env -D

作用:语法转换,源代码转换等 抽离:创建babel.congig.js文件单独配置预设,可以不在webpack.config.js的loader中配置了 webpack不会转换Importexport 语句以外的ES6代码,如果需要转义其他ES6代码还需要使用babel

  • babel-loader:作用是调用babel-core
  • @babel/core:本身只提供一个过程管理的功能,把源代码转成抽象语法树,进行遍历和生产,它本身也不知道具体要转换成什么语法,以及如何如何使用
  • @babel/preset-env:可以转换js语法,预设是插件的集合
  • @bable/cli:使用命令行才需要下载,否则在webpack这种工具中使用babel不需要安装这依赖包
  1. 先把Es6转换成es6语法树 (babel-core)
  2. 然后调用预设preset-env把ES6语法树转成ES5语法树(preset-env)
  3. 再把ES5语法树转成ES5代码(babel-core)

resolve

用于文件路径解析

SplitChunks(代码分离)

目的:将代码分离到不同的bundle中,之后可以按需加载,或者并行加载这些文件

问题:默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度

好处:代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能

module.exports = {
  entry: path.resolve(__dirname, './src/main.js')
  output: {
      // 所有文件的输出路径
      path: path.resolve(__dirname, './build'),
      // 打包输出的入口文件命名
      filename: "static/js/[name].js",
      // 给打包输出的其他文件命名 ([name].chunk.js 是为了和主文件区分)
      chunkFileName: 'static/js/[name].chunk.js',
      // 图片、字体等通过type: asset处理资源命名方式
      assetModuleFilename: "static/image/[hash:10][ext][query]"
  },
  optimization: {
    splitChunks: {
      // 如果是单页面应用把值修改为all,其他用默认值就好啦
      chunks: 'all',
    },
  },
};

dynamic import (动态导入)

另外一个代码拆分的方式是动态导入时,使用ECMAScript中的 import() 语法来完成,动态导入通常是一定会打包成独立的文件的

动态导入的文件命名,通常会在output中,通过 chunkFilename 属性来命名

默认情况下我们获取到的 [name] 是和id的名称保持一致的,修改name的值,可以通过magic comments(魔法注释)的方式

import(/* webpackChunkName: "home" */)

module.exports = {
  output: {
    path: resolve('build'),
    chunkFilename: 'chunk_[id]_[name].js',
  },
};

Terser(代码压缩)

npm install css-minimizer-webpack-plugin -D (css压缩)

对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置,需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  rules: [
     {
       test: /.s?css$/,
       use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
     },
  ],
  optimization: {
    minimize: true,
    minimizer: [
        new TerserPlugin(),
    ],
  },

  plugins: [
      new MiniCssExtractPlugin({
          filename: "static/css/[name].css",
          chunkFilename: "static/css/[name].chunk.css",
      }),         
      new CssMinimizerPlugin()
  ],
};

Tree Shaking (打包时消除无用代码)

mode 为 production 模式下,webpack默认的一些优化会带来很大额影响

webpack实现Tree Shaking采用了两种不同的方案:

  1. usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
  2. sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

场景:将mode设置为development,配合Terser一起优化才有效果

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
  optimization: {
    usedExports: true,
    minimize: true,
    minimizer: [new TerserPlugin()]
  },
};

CSS实现Tree Shaking

npm install purgecss-webpack-plugin -D

作用:删除未使用的CSS

  1. paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
  2. 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
const glob = require("glob");
const PurgeCSSPlugin = require("purgecss-webpack-plugin");

module.exports = {
  plugins: [
    // CSS Tree Shaking
    new PurgeCSSPlugin({
      paths: glob.sync(`${path.resolve(__dirname, "./src")}/**/*`, { nodir: true }),
      sadeList: function () {
          return {
              standard: ["html", "body"]
          }
      }
    }),
  ],
};

Prefetch和Preload 预获取和预加载

在声明 import 时,使用下面这些内置指令,来告知浏览器:
  1. prefetch(预获取):将来某些导航下可能需要的资源
  2. preload(预加载):当前导航下可能需要资源
prefetch 指令相比,preload 指令有许多不同之处
  1. preload chunk 会在父 chunk 加载时,以并行方式开始加载(prefetch chunk 会在父 chunk 加载结束后开始加载)
  2. preload chunk 具有中等优先级,并立即下载 (prefetch chunk 在浏览器闲置时下载)
  3. preload chunk 会在父 chunk 中立即请求,用于当下时刻 (prefetch chunk 会用于未来的某个时刻)

通过魔法注释来指定或者通过插件(preload-webpack-plugin) image.png

image.png