Webpack根底

Webpack从一个或多个入口(Entry)开始寻找相干的依赖关系来建设依赖图,而后辨认他们的内容,webpack通过加载器(Loader)来辨认模块的内容,再将内容整顿分类打包成不同的块,最初放到输入(Output)目录下。在这个过程中,能够应用一些有用的插件(Plugin)来辅助实现各种工作,例如注入环境变量、资产治理或打包优化等。

1.装置与运行

首先计算机中必须事后装置好node.js运行环境,再装置webpack依赖。

npm install --save-dev webpack webpack-cli

最初通过npx webpack即可执行打包工作。

2.外围配置

从4.0.0版本开始,Webpack不用用一个配置文件去捆绑你的我的项目,默认状况下,webpack应用src/index.js作为入口,输入的包是dist/main.js文件,然而也能够具体地配置各项参数来适配各自的需要。

2.1创立配置文件

这时候就须要在我的项目外面新增一个webpack.config.js文件,这里有个工具能够在线创立。

2.2多配置源

能够依据不同的状况抉择不同的配置文件:

"scripts": {  "build": "webpack --config prod.config.js"}

2.3编译指标

通过设置target属性,指定webpack编译输入的文件在什么样的环境中运行。例如服务器、浏览器别离可设置成"target": "node""target": "web"

2.4入口配置

默认状况下应用./src/index.js 作为默认入口。若要配置其余入口,可通过设置配置文件的 entry 属性来实现:

module.exports = {  entry: './path/to/my/entry/file.js',};

除了配置一个入口,还能够配置多个,多个入口就会对应多个输入,请确保每个输入的文件的名字要惟一,能够应用替换模版来确保名称的抵触。

{    entry: {        index: './index/file/path',        print: './print/file/path'    }}

以上配置完当前,就会生成两个入口块index和print。默认状况的名字是main。

entry属性能承受的数据结构有:门路字符串、门路字符串的数组、对象构造。

对于entry对象的具体配置如下:

  • dependOn: The entry points that the current entry point depends on. They must be loaded before this entry point is loaded.
  • filename: Specifies the name of each output file on disk.
  • import: Module(s) that are loaded upon startup.
  • library: Specify library options to bundle a library from current entry.
  • runtime: The name of the runtime chunk. When set, a new runtime chunk will be created. It can be set to false to avoid a new runtime chunk since webpack 5.43.0.
  • publicPath: Specify a public URL address for the output files of this entry when they are referenced in a browser. Also, see output.publicPath.

更多设置请查阅这里。

2.5输入配置

默认状况下应用./dist/main.js,若要改用其余设定,可设置配置文件的output属性来实现:

const path = require('path');module.exports = {  entry: './path/to/my/entry/file.js',  output: {    path: path.resolve(__dirname, 'dist'),    filename: 'my-first-webpack.bundle.js',  },};

此例子中,应用path来指定输入门路,__dirname指定的事以后文件所在的根目录。filename指定文件名称。

当须要动静的生成打包名称的时候,能够这样配置:

{    output: {        filename: '[name].bundle.js',    }}

在这个例子中,[name]将被文件的原名替换。

更多配置请查阅这里。

2.6加载器

通常状况下Webpack只能辨认JavaScript和JSON两种类型的文件,其余类型的模块要应用不同的载入器。总体上看,载入器的配置有两个:

  1. test确定哪些文件能够被转换;
  2. use 指定哪个载入器来执行这些转换。
module.exports = {  module: {    rules: [{ test: /\.txt$/, use: 'raw-loader' }],  },};

留神:test 属性前面的正则表达式没有引号,示意蕴含.txt结尾的文件,若蕴含引号则示意的是绝对路径,即以.txt结尾的文件。

2.7插件

插件是用来执行一系列工作来辅助实现整个打包过程,例如打包优化、资产治理、注入环境变量等。如果要应用一个插件,首先要用require()将它导入,而后再增加到配置文件的plugins属性数组外面。

插件实质上是一个领有apply办法的javascript对象,apply办法可能被webpack编译器调用,且能拜访整个汇编周期。

有很多插件能够在这里找到,还有很多用例值得摸索,相干信息在这里找到。

插件能够传入参数,所以在应用中,能够通过传入参数来新建一个实例,在把这个实例传入webpack.config.jsplugins属性里。

2.8模式

预设的有三种模式:development, production, none,每种模式都有不同的优化细度。默认状况是production

module.exports = {  mode: 'production',};

更多配置请查看这里。

2.9NPM脚本

在package.json文件外面设置scripts脚本,能够更不便为当前提供cli命令。例如:

"scripts": {    "test": "echo \"Error: no test specified\" && exit 1"    "build": "webpack"   },

当配置好build当前,能够通过命令行间接输出npm run build即可执行对应的命令:npx webpack

任何参数(包含webpack.json外面的参数)都能够加在脚本的前面,后面带上--,例如"build": "webpack --mode=production"

3.资产治理

webpack会将我的项目所有相干的依赖文件都打包到一起(没有用到的除外),默认状况下,webpack只能辨认javascript和json两种文件(外部曾经有了这两种文件的载入器或者资产模块反对),其余类型的文件还须要配置特定的加载器。

多个加载器,能够被前后链接,后面解决的后果能够被前面的加载器应用,直到最初一个加载器返回后果。

webpack外部的资产模块(asset modules),是一种不须要配置额定加载器的模块,能够用来解决字体、图标等文件的模块。次要有以下:

  1. asset/resource, 收回一个独自的文件并导出URL
  2. asset/inline,导出资产的数据URI
  3. asset/source, 导出资产的源代码
  4. asset, 主动抉择导出数据URI和收回一个独自的文件。

通常,资产文件都是全局的放在一个asset目录之下,然而也能够把一些高度耦合的文件放在与它们关联的模块放在一起,这样打包进去的“公有”资产文件就会更加严密的与关联的模块放在一起。

3.1载入CSS

webpack默认状况无奈辨认css文件,因而须要装置一个加载器:

npm install --save-dev style-loader css-loader

而后在webpack的模块局部进行相干配置:

const path = require('path'); module.exports = {   entry: './src/index.js',   output: {     filename: 'bundle.js',     path: path.resolve(__dirname, 'dist'),   },  module: {    rules: [      {        test: /\.css$/i,        use: ['style-loader', 'css-loader'],      },    ],  }, };

在以上例子中,css文件应用了两个加载器来加载,别离是styled-loadercss-loader,前一个解决的后果作为后一个的输出,最初由css-loader返回的后果作为css文件加载的最终后果。

当配置结束当前间接运行npx webpack就可实现打包。

3.2载入图片

能够间接应用资产模块来加载图片:

{  test: /\.(png|svg|jpg|jpeg|gif)$/i,  type: 'asset/resource'}

所有配置类型的图片都会被加载器解决,而后放到输入目录下。任何援用图片的变量都会蕴含得有被解决的这些图片的url地址。

3.3载入字体

同样的,字体也能够间接应用资产模块来加载:

{  test: /\.(woff|woff2|eot|ttf|otf)$/i,  type: 'asset/resource',}

通过在我的项目中引入字体文件(woff和woff2),再在css文件中引入,例如:

@font-face {  font-family: 'MyFont';  src: url('./my-font.woff2') format('woff2'),    url('./my-font.woff') format('woff');  font-weight: 600;  font-style: normal;} .hello {   color: red;   font-family: 'MyFont';   background: url('./icon.png'); }

最初间接打包我的项目即可。

3.4载入数据

webpack默认是反对加载json文件的,然而其余的数据文件,例如CSVs, TSVsXML这些文件,就须要应用其余加载器。

npm install --save-dev csv-loader xml-loader

在webpack中配置:

{  test: /\.(csv|tsv)$/i,  use: ['csv-loader'],},{  test: /\.xml$/i,  use: ['xml-loader'],},

在我的项目里导入这类数据当前,运行打包命令即可。

其余类型的数据,例如toml, yaml, json5等,能够加载后作为JSON模块来应用,然而要应用到自定义的解析器

npm install toml yamljs json5 --save-dev

而后在webpack中增加配置:

{  test: /\.toml$/i,  type: 'json',  parser: {    parse: toml.parse,  },},{  test: /\.yaml$/i,  type: 'json',  parser: {    parse: yaml.parse,  },},{  test: /\.json5$/i,  type: 'json',  parser: {    parse: json5.parse,  },},

最终运行打包命令即可。

4.输入治理

4.1HtmlWebpackPlugin

如果要更改webpack的入口,而不想动输入目录下index文件外面的旧援用。则须要配置应用HtmlWebpackPlugin插件。

npm install --save-dev html-webpack-plugin

而后在web pack.config.js文件外面配置:

const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {  ...  plugins: [    new HtmlWebpackPlugin({      title: 'Output Management',    }),  ]};

通过这样的配置当前,运行打包,此插件将在输入目录下生成一个index.html文件(若文件已存在,将代替原文件)。

更多无关配置,请查看这里。

4.2清理/dist目录

默认状况家,/dist目录外面的文件不会被清理,每次运行打包,生成的新文件和旧文件会混合在一起。为了让每次打包都主动的清理旧的文件,能够通过配置来实现。

{    output: {        ...        clean: true    }}

4.3生成清单(Manifest)

如果要理清开发的模块和生成的目录下的文件的对应关系,能够应用WebpackManifestPlugin插件。

5.开发工具

5.1源代码映射

当我的项目被打包到一个文件(bundle.js)外面当前,源文件外面蕴含有谬误,那么在bundle.js文件里的栈追踪就会很艰难。为了更容易地找到问题和正告,javascript提供了源映射(source map),它能映射编译后的代码和源代码。

简略的配置如下:

module.exports = {    ...    devtool: "inline-source-map",}

编译当前,在浏览器中关上,任何问题都能映射到源代码中。下面例子中的inline-source-map是远吗映射中的一个工具,还有很多,能够参阅这里。

5.2开发选项

如果在开发过程中每次增加代码或者批改代码都要去运行npm run build, 那么开发效率将会很低下。webpack提供了几个配置选项供开发者应用:

  1. watch mode
  2. webpack-dev-server
  3. webpack-dev-middleware
5.3监督模式

有多种形式启动这个模式,能够间接在webpack.config.js文件里使能watch:true,当我的项目在第一次编译当前,我的项目就会继续去监督变动过程。另外一只是间接在命令行启动过程中增加参数npx webpack --watch

另外,在webpack-dev-server和web pack-dev-middleware两种开发工具激活的状况下,监督模式是主动关上的。

5.4webpack-dev-server

这个工具为开发者提供一个根本的web服务器,可能实时的重加载。

npm install --save-dev webpack-dev-server

批改配置文件web pack.config.js:

module.exports={    devServer: {    static: './dist',  },  optimization: {      runtimeChunk: 'single'  }}

如果我的项目中有多个入口则须要配置Optimization.runtimeChunk: 'single'以防止运行时谬误。

最初在package.json文件的script项增加运行命令:

"scripts": {  "start": "webpack serve --open"}

更多对于dev-server的配置,请参阅这里。

5.5webpack-dev-middleware

这个包装器会把解决的文件发射到服务器。它是在webpack-dev-server外面应用的,然而它能够作为独立的包来反对自定义的设置。以下例子演示联合一个express服务器的用例(把文件发射到express服务器里):

npm install --save-dev express webpack-dev-middleware

设置webpack.config.js配置:

module.exports = {    ...    output: {        ...        publicPath: '/'    }}

在脚本里应用的publicPath确保了文件在http://localhost:3000上运行正确,下一步配置express服务器:

在根目录下创立server.js文件:

const express = require('express');const webpack = require('webpack');const webpackDevMiddleware = require('webpack-dev-middleware');const app = express();const config = require('./webpack.config.js');const compiler = webpack(config);// Tell express to use the webpack-dev-middleware and use the webpack.config.js// configuration file as a base.app.use(  webpackDevMiddleware(compiler, {    publicPath: config.output.publicPath,  }));// Serve the files on port 3000.app.listen(3000, function () {  console.log('Example app listening on port 3000!\n');});

最初在package.json文件里增加配置,并运行npm run server就能够了。

{    "scripts": {        "server": "node server.js"    }}
无关热模块替换的应用,请参阅这里。

6.代码宰割

当打包的代码过大,影响性能和效率的时候,就能够把打包的程序宰割成多个小包,分状况加载。罕用的代码宰割形式有:

  • 多入口(entry point)
  • 避免反复(prevent duplication)
  • 动静导入(dynamic imports)

6.1多入口

间接在webpack外面配置多个入口即可。然而这种形式有两个毛病:

  1. 如果多个入口块(entry chunks)之间反复地用了多个模块,则这些模块都将被蕴含在这些块里;
  2. 不够灵便,且不能用外围应用逻辑来动静的宰割代码

6.2避免反复

通过配置dependOn选项来共享多个包之间的模块,当有多个入口块时,还须要配置optimization.runtimeChunk:'single'

const path = require('path');module.exports = {  mode: 'development',  entry: {   index: './src/index.js',   another: './src/another-module.js',   index: {     import: './src/index.js',     dependOn: 'shared',   },   another: {     import: './src/another-module.js',     dependOn: 'shared',   },   shared: 'lodash',  },  output: {    filename: '[name].bundle.js',    path: path.resolve(__dirname, 'dist'),  },  optimization: {    runtimeChunk: 'single'  }};

通过以上的配置,shared会是一个独自的块,其余依赖它的块都会从它这里获取援用。

6.3提取公共依赖

应用它能够把公共的依赖提取进去放到一个现有的入口块或者新的入口块外面,通过SplitChunksPlugin实现。

const path = require('path');module.exports = {    ...    optimization: {        splitChunks: {            chunks: 'all',        },    },};

6.4动静导入

动静导入次要有两种办法,第一种,也是举荐的办法,应用import()语法;第二种,采纳require.ensure

6.5包剖析

这里有一些官网的剖析工具。

7.ESM模块反对

默认状况下,webpack会把ESM模块导出成其它类型的模块,也会把其余模块导入成ESM类型。webpack也能自动检测文件是ESM类型还是其余类型。

NodeJs在package.json文件外面预留了"type":"mode"来强制将所有当前目录之下的文件转换成ESM模块,果"type":"commonjs",则将所有转化成commonjs类型。

7.1反对TypeScript

首先装置相干依赖:

npm install --save-dev typescript ts-loader

我的项目中增加tsconfig.json文件,而后配置以下必备内容:

{  "compilerOptions": {    "outDir": "./dist/",    "noImplicitAny": true,    "module": "es6",    "target": "es5",    "jsx": "react",    "allowJs": true,    "moduleResolution": "node"  }}

最初在webpack.config.json文件中增加配置:

const path = require('path');module.exports = {  entry: './src/index.ts',  module: {    rules: [      {        test: /\.tsx?$/,        use: 'ts-loader',        exclude: /node_modules/,      },    ],  },  resolve: {    extensions: ['.tsx', '.ts', '.js'],  },};

8.缓存

为了放慢服务器读取我的项目的工夫,缩小网络流量,webpack提供缓存机制。

8.1文件名字

能够应用output.filename来定义输入的文件的名字。webpack提供了一种应用括号括起来的名字作为模版文件名的办法,叫做替换法(substitutions)。[contenthash]将会被基于文件内容的哈希名替换。文件中的任何内容扭转之后,哈希名都会扭转。同样的,[name]将会被原文件的名字替换掉。

8.2提取样板

利用splitChunksPlugin插件将运行时代码分离出来放到一个隔离的块外面。只须要配置optimization.runtimeChunk选项single即可。

module.exports = {    "optimization": {        "runtimeChunk": "single"    }}

如果要把第三方的库剥离进去放到一个独自的包里(vendor),就能进步开发效率,用户就能够始终缓存着第三方的库,更新那些常常扭转的包。要达到这样的成果,只须要通过以下配置即可:

module.exports = {      optimization: {      runtimeChunk: "single",      splitChunks: {         cacheGroups: {           vendor: {             test: /[\\/]node_modules[\\/]/,             name: 'vendors',             chunks: 'all',           },         },       },        }}

8.3模块标识符

如果我的项目中任何地位的代码产生了变动,将引起整个我的项目生成的文件都变动,因为每个模块的标识符默认基于解析程序都会自增,即每次解析程序扭转ID都会扭转。为了避免这样的状况,webpack提供以下配置:

module.expects = {    //...    optimization: {        moduleIds: 'deterministic'    }}

9.环境变量

通过CLI设定的环境变量能够被webpack.config.js配置文件读取到,从而有针对性的运行我的项目。

环境变量在CLI中的设定是通过在--env前面跟上变量设定来实现的。

webpack.config.js

const path = require('path');module.exports = (env) => {  // Use env.<YOUR VARIABLE> here:  console.log('Goal: ', env.goal); // 'local'  console.log('Production: ', env.production); // true  return {    entry: './src/index.js',    output: {      filename: 'bundle.js',      path: path.resolve(__dirname, 'dist'),    },  };};

10.解析

这个选项用来配置模块解析的形式。通常的模式如下:

module.exports = {  //...  resolve: {    // configuration options  },};

10.1别名

创立别名,以更容易地导入模块。例如创立以下门路的别名:

const path = require('path');module.exports = {  //...  resolve: {    alias: {      Utilities: path.resolve(__dirname, 'src/utilities/')    },  },};

如上应用Utilities代表src/utilities/门路,在须要导入的中央就能够间接应用别名:

import Utility from 'Utilities/utility';

11.模块

在模块化编程中,开发人员将程序分解成离散的功能块,叫做模块。编写良好的模块提供了松软的形象和封装边界,每个模块在整个利用中具备连贯的设计和明确的指标。