01webpack基础知识

发布时间 2023-10-09 17:25:36作者: songxia777

1 概述

1.1 什么是 webpack

1、 webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。在webpack看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)

2、 webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js 文件、css 文件等)都看成模块,通过 loader(加载器)和 plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源

3、 webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

4、 webpack 更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能

1.2 webpack的5个核心概念

1.2.1 入口(entry)

入口起点(entry point)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图

默认值是 ./src/index.js,但是可以通过配置配置文件进行配置,来指定一个或多个不同的入口起点。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的

例如:webpack.config.js

module.exports = {
  entry: './src/index.js'
};

1 入口方式

单个入口
module.exports = {
  entry: './path/to/my/entry/file.js',
};
数组多个入口

可以将一个文件路径数组传递给 entry 属性

module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',
  },
};
对象语法

这是应用程序中定义入口的最可扩展的方式

“webpack 配置的可扩展” 是指,这些配置可以重复使用,并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如 webpack-merge)将它们合并起来

module.exports = {
  entry: {
    app1: './src/app.js',
    adminApp: './src/adminApp.js',
    another:{
      dependOn: 'app1', // 依赖于 app1 chunk
      import: './src/app2.js',
    }  
  },
};

描述入口的对象属性:

  • dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。
  • filename: 指定要输出的文件名称。
  • import: 启动时需加载的模块。
  • library: 指定 library 选项,为当前 entry 构建一个 library。
  • runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
  • publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址

2 常见使用场景

分离 app(应用程序) 和 vendor(第三方库) 入口
module.exports = {
  entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
   output: {
    // 配置输出   
    filename: '[name].[contenthash].bundle.js',
  },
};
多页面应用程序
module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js',
  },
};

1.2.2 输出(output)

output 指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。也可以通过在配置中指定一个 output 字段,来配置这些处理过程

例如:webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    // path:输出文件目录,路径必须是绝对路径, dist输出文件目录  
    path: path.resolve(__dirname, 'dist'),
    // 文件名  
    filename: 'js/bundle.[contenthash].js',
      
    // 引入所有公共资源的前缀
    publicPath:'/',
      
    // 给其他单独打包的文件 命名,非入口 js 文件
    // 一般是 抽离出来的 其他模块 js 文件  
    chunkFilename:'[name]_chunk.js'  
  },
};

path

  1. 所有文件的输出文件的目标路径

  2. 必须是绝对路径

  3. 打包后项目文件在硬盘中的存储位置

  4. HtmlWebpackPlugin生成的html文件,都会存放在以path为基础的目录下

publicPath

  1. 输出解析文件的目录,指定资源文件引用的目录 ,打包后浏览器访问服务时的 url 路径中通用的一部分

  2. publicPath 并不会对生成文件的路径造成影响,主要是对项目页面里面引入的资源的路径做对应的补全,常见的就是css文件里面引入的图片

1.2.3 loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中

在 webpack 的配置中,loader 有两个属性:

1、 test 属性,识别出哪些文件会被转换

2、 use 属性,定义出在进行转换时,应该使用哪个 loader

const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [
        { 
            test: /\.txt$/, 
            use: 'raw-loader'
        }
    ],
  },
};

loader的主要作用

1、用于对模块的源代码进行转换

2、 loader 可以使你在 import 或"加载"模块时预处理文件

3、 loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL

4、 loader 允许你直接在 JavaScript 模块中 import CSS文件!

1.2.4 Plugins

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。

多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件

module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

1.2.5 模式 mode

通过选择 development, productionnone 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production

module.exports = {
  mode: 'production'
}
选项 描述 特点
development 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development。启用 NamedChunksPluginNamedModulesPlugin 能让代码本地调试运行的环境
production 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin 能让代码优化上线运行的环境

1.2.6 浏览器的兼容性

Webpack 支持所有符合 ES5 标准 的浏览器(不支持 IE8 及以下版本)。webpack 的 import()require.ensure() 需要 Promise

1.2.7 环境

Webpack 5 运行于 Node.js v10.13.0+ 的版

2 package.json 文件

package.json 文件主要是显示项目的名称、版本、作者、协议等信息

2.1 script 常用命令

dev": "webpack --mode development --config webpack.config.js --progress --colors"

// 指定 环境变量 production development
--mode development

// 可以传递 环境变量参数 到 webpack.config.js 中
--env development
// webpack.config.js 中接收
module.exports = env => {
  console.log(env)
}


// 指定 要加载的 配置文件 --config 可以简写为 -c
--config

// 显示编译过程和百分比
--progress

// 编译结果显示不同的颜色
--colors

2.2 参数

字段name和version

在package.json中最重要的就是name和version字段

他们都是必须的,如果没有就无法install

name和version一起组成的标识在假设中是唯一的

改变包应该同时改变version

字段description

项目的描述,字符串类型

字段keywords

项目的关键字

字段homepage

项目官网的url

字段license

指定项目的许可证,可以使人知道使用的权利和限制的

字段scripts

“scripts”是一个由脚本命令组成的hash对象,他们在包不同的生命周期中被执行

3 webpack 优化配置

3.1 概述

webpack的性能优化,主要包含 开发环境性能优化和 生产环境性能优化。

开发环境性能优化包含

  • 优化打包构建速度:HMR
  • 优化代码调试:source-map

生产环境性能优化包含

  • 优化打包构建速度:oneOf、babel缓存、多进程打包、externals、dll
  • 优化代码运行的性能:缓存(contenthash)、tree shaking 、代码分割(code split)、懒加载/预加载 、PWA

3.2 HMR 模块热更新

样式文件:本身就可以使用HMR功能,因为style-loader内部实现了

js文件:默认不能使用HMR功能: 需要修改js代码,添加支持HMR功能的代码(只能处理非入口js文件的其他文件)

html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了~ (其实也不用做HMR功能)

解决方案:修改entry入口,将html文件引入

3.2.1 webpack.config.js 配置

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 001TODO: 将 要监控的html文件加入 入口中,可以实现 实时监控的功能
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    filename: 'build.js',
    path: resolve(__dirname, 'build'),
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.s[a|c]ss$/i,

        use: [
          'style-loader',
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  },
  // 配置本地服务
  devServer: {
    static: resolve(__dirname, './dist'),

    compress: true,

    // 002TODO: 启用 HMR
    hot: true,
 
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
}

3.2.2 非 入口 print.js 文件 监控

// 引入
import print from './print';

console.log('index.js文件被加载了~');

print();

function add(x, y) {
  return x + y;
}

console.log(add(1, 3));

if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
  });
  
 // 也可以不加回调函数   
 module.hot.accept('./print.js');  
}

3.3 source-map

source-map:外联

可以追踪 错误代码准确信息 和 源代码的错误位置

inline-source-map:内联

可以追踪 错误代码准确信息 和 源代码的错误位置

只生成一个内联source-map

hidden-source-map:外部

错误代码错误原因,但是没有错误位置

不能追踪源代码错误,只能提示到构建后代码的错误位置

eval-source-map:内联

每一个文件都生成对应的source-map,都在eval

错误代码准确信息 和 源代码的错误位置

nosources-source-map:外部

错误代码准确信息, 但是没有任何源代码信息

cheap-source-map:外部

错误代码准确信息 和 源代码的错误位置

只能精确的行

cheap-module-source-map:外部

错误代码准确信息 和 源代码的错误位置

module会将loader的source map加入

总结:

开发环境建议:eval-source-map eval-cheap-module-souce-map

生产环境建议:source-map cheap-module-souce-map

在生产环境中,如果只想定位行数,又不想暴露源码,可以 使用 nosources-source-map

一般在生产环境中,都是建议直接关闭 source-map

3.4 oneOf

正常来讲,一个文件只能被一个loader处理,当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:先执行 eslint 在执行babel

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
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',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
            
          {
            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 HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

3.5 缓存

3.5.1 babel 缓存

让第二次打包构建速度更快,设置 cacheDirectory: true

关闭缓存文件压缩,cacheCompression:false

{
         test: /\.js$/,
         exclude: /node_modules/,
         loader: 'babel-loader',
         options: {
           presets: [
             [
               '@babel/preset-env',
               {
                 useBuiltIns: 'usage',
                 corejs: { version: 3 },
                 targets: {
                   chrome: '60',
                   firefox: '50'
                 }
               }
             ]
           ],

          // 开启babel缓存-优化构建效率, 第二次构建时,会读取之前的缓存
            cacheDirectory: true,
            // 关闭缓存文件压缩 
            cacheCompression:false 
         }
       },

3.5.2 ESLint 缓存

  plugins: [
    new ESLintPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
      // 排除 node_modules 中js文件的检查
      exclude: 'node_modules', // 默认值
      // 开启缓存
      cache: true,
      // eslint 缓存 存储的目录位置
      cacheLocation: path.resolve(__dirname, './node_modules/.cache/eslintcache')
    }),
  ],

3.5.3 文件资源缓存

hash

每次wepack构建时会生成一个唯一的hash值

但是因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效

chunkhash

根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk

contenthash-推荐

根据文件的内容生成hash值。不同文件hash值一定不一样

让代码上线运行缓存更好使用

3.5.6 runtimeChunk

当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)

但是 index.js 文件的 hash 值也发生了变化,这会导致 index.js 的缓存失效。

解决方案:将 hash 值单独保管在一个 runtime 文件中

我们最终输出三个文件:index、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,index不变。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小

 optimization: {
    splitChunks: {
      chunks: 'all',
      // 缓存第三方库 设置
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },

    // 提取runtime文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
    },
  },

3.6 tree shaking

主要功能是:去除无用代码,减少代码体积

但是主要前提是:1、 必须使用ES6模块化 2、 开启production环境

在package.json中配置 sideEffects:

"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking),但是 也会把 css 或者 @babel/polyfill 等文件去掉,所以,一般可以采用下面的代码设置

"sideEffects": ["*.css", "*.less"]

webpack5 基本已经内置配置优化了 tree shaking

3.7 代码分割 code split

3.7.1 设置多入口

可以将入口 设置为 对象的方式,可以设置多个入口文件

  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },

3.7.2 设置 optimization splitChunks

   // 抽离 第三方
    splitChunks: {
      chunks: 'all',
      // 缓存第三方库 设置
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors/vendors',
          chunks: 'all',
        },
      },
    },

单入口

1、 可以自动将node_modules中代码单独打包一个chunk最终输出

2、 可以 将 import 动态引入的模块 自动单独打包

动态导入 import

import动态导入语法:能将某个文件单独打包

test.js 文件:通过js代码,让某个文件被单独打包成一个chunk

// webpackChunkName: 'test' 魔法注释,设置 模块的名称
import(/* webpackChunkName: 'test' */'./test')
  .then(({ sum }) => {
    console.log('文件加载成功',sum(2,3));
  })
  .catch(() => {
    console.log('文件加载失败~');
  });

多入口

可以自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk

3.8 懒加载和预加载

懒加载: 当文件需要时才按需加载(告诉浏览器立即加载资源)

预加载:prefetch 等其他资源加载完毕,浏览器空闲了,再加载资源(告诉浏览器在空闲时才开始加载资源)

  • Preload加载优先级高,Prefetch加载优先级低。
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源
console.log('index.js文件被加载了~');

document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

懒加载 preload 和 预加载 prefetch 有浏览器兼容性问题

3.9 dll

使用 DllPlugin 为更改不频繁的代码生成单独的编译结果。这可以提高应用程序的 编译速度,尽管它增加了构建过程的复杂度

webpack.dll.js

/*
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
    当你运行 webpack 时,默认查找 webpack.config.js 配置文件
    需求:需要运行 webpack.dll.js 文件
      --> webpack --config webpack.dll.js
*/

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

webpack.config.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'production'
};

3.10 Include/Exclude

开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件

include:包含,只处理 xxx 文件

exclude:排除,除了 xxx 文件以外其他文件都处理

二者不可以同时

{
    test: /\.js$/,
    // exclude: /node_modules/, // 排除node_modules代码不编译
    include: path.resolve(__dirname, "../src"), // 也可以用包含
    loader: "babel-loader",
},

4 其他配置

4.1 resolve

// 解析模块规则的配置
resolve:{
    // 配置解析模块路径的别名,可以极大的简化路径的书写,但是就不会有路径提示了
    alias:{
        '@':path.resolve(__dirname,'src')
    },
    // 配置省略路径文件的后缀名   
    extensions:['.js','.json','.jsx']   
    
    // 配置解析模块寻找的指定目录
    modules:[path.resolve(__dirname,'../../node_modules')]
}