likes
comments
collection
share

Webpack入门教程(二):基础用法

作者站长头像
站长
· 阅读数 18

前言

在上一章我们对 webpack 有了一个初体验,从这章开始我们就来正式了解一下 webpack 的一些核心概念。

webpack 有很多重要的概念,一般认为 webpack 的核心概念包括入口(entry)、输出(output)、加载器(loader)、插件(plugins)和模式(mode)共计 5 个。

1 入口 Entry

入口起点(entry point) 定义 webpack 从哪个文件开始构建依赖关系图,进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

它的默认值是 ./src/index.js,但是我们可以通过在配置文件中配置 entry属性来指定一个(或多个)不同的入口起点。例如:

1.1 单个入口(简写)语法

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

这种单入口写法是下面的简写

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

1.2 多入口语法

我们可以通过给 entry 属性传递数组、对象或者函数来实现,例如:

module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js']
};

或者

module.exports = {
  entry: {
    app: './src/app.js',
    adminApp: './src/adminApp.js',
  },
};

1.3 描述入口的对象

用于描述入口的对象。你可以使用如下属性:

  1. dependOn: 默认情况下,每个入口 chunk 保存了全部其用的模块(modules)。使用 dependOn 选项你可以与另一个入口 chunk 共享模块:
module.exports = {
  //...
  entry: {
    app: { import: './app.js', dependOn: 'react-vendors' },
    'react-vendors': ['react', 'react-dom', 'prop-types'],
  },
};

这样,app 这个 chunk 就不会包含 react-vendors 拥有的模块了。

dependOn 选项的也可以为字符串数组。

  1. filename: 默认情况下,入口 chunk 的输出文件名是从 output.filename 中提取出来的,但你可以为特定的入口指定一个自定义的输出文件名。(Output 我们后面会讲)
module.exports = {
  //...
  entry: {
    app: './app.js',
    home: { import: './contact.js', filename: 'pages/[name][ext]' },
    about: { import: './about.js', filename: 'pages/[name][ext]' },
  },
};
  1. import: 指定启动时需加载的模块,可以传递字符串或者一个数组。
module.exports = {
  //...
  entry: {
    app: { import: ['./app.js', './app2.js'] }
};
  1. library: 指定 library 选项,为当前 entry 构建一个 library

使用 library 属性指定库名称时,entry 中的代码将打包为一个单独的文件,并暴露出该库的全局变量。以下是一个示例:

module.exports = {
  entry: {
    main: {
      import: "./src/index.js",
      library: {
        name: "HelloWorld",
        type: "global",
      },
    },
  },
};

在上面的例子中,我们使用了 library.name 定义了库名称,library.type 指定了方式,type: 'global' 表示会将库挂载到全局对象 window 上。

然后就可以使用 HelloWorld 全局变量来访问该库的方法和属性。例如:

<head>
  <script src="./dist/HelloWorld.js"></script>
</head>

<body>
  <script>
    HelloWorld.sayHello();
  </script>
</body>
  1. runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
  2. publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。请查看 output.publicPath

chunk: 在 webpack 中,chunk 可以理解为经过 webpack 分割出来的代码块。具体来说,当打包项目时,webpack 会将入口文件及其依赖项(import、require)全部打包在一起。当项目变得庞大时,打包成单个 bundle.js 文件可能会导致加载时间变长,因为浏览器需要下载整个文件才能执行应用程序。这就是 webpack 开发人员决定将代码拆分成更小的“chunks”的原因。

2 输出 Output

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

2.1 基础用法

webpack 配置中,output 属性的最低要求是,将它的值设置为一个对象,然后为将输出文件的文件名配置为一个 output.filename,但是一般我们还会想要自定义生成的路径,这个时候就可以使用 path 这个属性:

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

在上面的示例中,我们通过 output.filenameoutput.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。

2.2 多个入口起点

如果配置中创建出多于一个 "chunk",则应该使用 占位符(substitutions) 来确保每个文件具有唯一的名称。

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js',
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist',
  },
};

像上面的配置就会生成两个chunk,一个叫 app.js,一个叫 search.js

常见的占位符有

  1. [name]:模块名称,对于entry定义的每个入口点,设置该选项可以确保生成唯一的文件名。
  2. [id]:模块标识符,通常是从0开始自增的整数。
  3. [chunkhash]:代码块内容的哈希值,每个代码块有一个独特的哈希值。当在生产环境使用时,可用于实现长期缓存。
  4. [contenthash]:生成文件的内容哈希值,在文件内容发生变化时,该哈希值也会变化。
  5. [hash]:compilation对象的哈希值,基于当前构建过程中所有webpack特定的信息计算而来,任意文件修改都会导致哈希值的变化。

这些占位符可以用来创建具有唯一性和可追踪性的文件名,并且可用于实现长期缓存等功能。例如,使用 [hash] 占位符将文件名设置为 main.[hash].js 将确保在每次构建过程中该文件名都会发生变化,即使只改动了单个文件的内容。

3 加载器 Loader

loaderwebpack 一个很重要的概念,它用于将非 javascript 文件(如图像、样式等)转换为 webpack 能够理解和打包的模块

webpack 中,每个文件都被视为模块,并且在构建时会根据依赖关系进行打包。但是,webpack 只能理解 javascriptjson 格式,因此需要使用 loader 来处理其他类型的文件,并将它们转化为 webpack 的模块。

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

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader

3.1 示例

例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。为此,首先安装相对应的 loader

npm install --save-dev css-loader ts-loader

然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.ts$/, use: 'ts-loader' },
    ],
  },
};

以上配置中,对一个单独的 module 对象定义了 rules 属性,rules 是一个数组,里面包含两个对象,每个对象两个必须属性:testuse。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.css'/'.ts' 的路径」时,在你对它打包之前,先 use(使用) css-loader/ts-loader转换一下。”

3.2 使用 loader 的两种方式

  1. 方式一:配置方式(推荐):在 webpack.config.js 文件中指定 loader

module.rules 允许我们在 webpack 配置中指定多个 loader。 这种方式是展示 loader 的一种简明方式,并且有助于使代码变得简洁和易于维护。同时让你对各个 loader 有个全局概览:

loader 从右到左(或从下到上) 地取值(evaluate)/执行(execute)。在下面的示例中,从 sass-loader 开始执行,然后继续执行 css-loader,最后以 style-loader 为结束。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader'  },
          { loader: 'sass-loader' },
        ],
      },
    ],
  },
};
  1. 方式二:内联方式:在每个 import 语句中显式指定 loader

下面是关于内联 Loader 常见的标记和它们的含义:

  1. !(感叹号)用于分隔多个 Loader,表示在当前模块中使用多个 LoaderLoader 的执行顺序是从右往左即从后往前执行。例如:
import Style from 'style-loader!css-loader!./styles.css'

表示在 ./styles.css 模块中先使用 css-loader,再使用 style-loader

  1. ?(问号):用于向 loader 传递参数。参数的格式是键值对的形式,多个参数之间使用 & 分隔。例如:
import Style from('style-loader!css-loader?modules&localIdentName=[name]__[local]')

表示在使用 css-loader 时传递了两个参数,moduleslocalIdentName

  1. !(感叹号前缀)将禁用所有已配置的 normal loader(普通 loader),例如:
import Style from '!style-loader!css-loader!./styles.css'

表示在 ./styles.css 模块中只使用 style-loadercss-loader,不执行配置文件中的普通 loader

  1. !!(双感叹号)用于禁用所有已配置的 loader(preLoader, loader, postLoader),例如:
import Style from '!!style-loader!css-loader!./styles.css'
  1. -!(减号和感叹号)禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoaders,例如:
import Style from '-!style-loader!css-loader!./styles.css'

这些内联 loader 的标记在 webpack 中可以用来灵活地控制 loader 的执行顺序、跳过配置文件中的 loader、禁用配置文件中的 loader,并向 loader 传递参数,从而满足不同的需求。但需要注意的是,过多地使用内联loader 可能会导致代码难以维护和理解,建议在实际项目中谨慎使用。

注意在 webpack v4 版本可以通过 CLI 使用 loader,但是在 webpack v5 中被弃用。

3.3 loader 的特性

  1. loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript
  2. loader 可以是同步的,也可以是异步的。
  3. loader 运行在 Node.js 中,并且能够执行任何操作。
  4. loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。
  5. 除了常见的通过 package.jsonmain 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。
  6. 插件(plugin)可以为 loader 带来更多特性。
  7. loader 能够产生额外的任意文件。

4 资源模块 Asset Module

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

webpack 5 之前,通常使用:

  1. raw-loader 将文件导入为字符串
  2. url-loader 将文件作为 data URI 内联到 bundle
  3. file-loader 将文件发送到输出目录

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader

  1. asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  2. asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  3. asset/source 导出资源的源代码。之前通过使用 raw-loader 实现
  4. asset 在导出一个 data URI发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为 'javascript/auto' 来解决。

4.1 Resource 资源

webpack.config.js

module.exports = {
 ...
 module: {
   rules: [
     {
       test: /\.png/,
       type: 'asset/resource'
     }
   ]
 },
};

src/index.js

import mainImage from './images/main.png';

img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'

所有 .png 文件都将被发送到输出目录,并且其路径将被注入到 bundle 中。

默认情况下,asset/resource 模块以 [hash][ext][query] 文件名发送到输出目录。

可以通过在 webpack 配置中设置 output.assetModuleFilename 来修改此模板字符串:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource'
      }
    ]
  },
};

另一种自定义输出文件名的方式是,将某些资源发送到指定目录:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource'
     }
     },
     {
       test: /\.html/,
       type: 'asset/resource',
       generator: {
         filename: 'static/[hash][ext][query]'
       }
     }
    ]
  },
};

使用此配置,所有 html 文件都将被发送到输出目录中的 static 目录中。

4.2 inline 资源(inlining asset)

webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
       test: /\.svg/,
       type: 'asset/inline'
     }
    ]
  }
};

src/index.js

 import metroMap from './images/metro.svg';

 block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)

所有 .svg 文件都将作为 data URI 注入到 bundle 中。

4.3 source 资源(source asset)

webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
       test: /\.txt/,
       type: 'asset/source',
     }
    ]
  }
};

src/example.txt

Hello world

src/index.js

import exampleText from './example.txt';

block.textContent = exampleText; // 'Hello world'

所有 .txt 文件将原样注入到 bundle 中。

4.4 通用资源类型

webpack.config.js

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
       test: /\.txt/,
       type: 'asset',
      }
    ]
  },
};

现在,webpack 将按照默认条件,自动地在 resourceinline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。

可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.txt/,
        type: 'asset',
       parser: {
         dataUrlCondition: {
           maxSize: 4 * 1024 // 4kb
         }
       }
      }
    ]
  },
};

5 插件 Plugin

插件webpack支柱 功能。Webpack 自身也是构建于你在 webpack 配置中用到的 相同的插件系统 之上!

插件目的在于解决 loader 无法实现的其他事Webpack 提供很多开箱即用的 插件

5.1 插件的本质

webpack 插件本质上是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。

ConsoleLogOnBuildWebpackPlugin.js

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('webpack 构建正在启动!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

compiler hooktap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中重复使用。

5.2 插件的使用

由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入一个 new 实例。

取决于你的 webpack 用法,对应有多种使用插件的方式。

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

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

ProgressPlugin 用于自定义编译过程中的进度报告,HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 my-first-webpack.bundle.jsJS 文件。

6 构建模式 Mode

Webpack 有三种模式:developmentproductionnone。通过设置不同的模式,可以启用不同的内置优化。

6.1 三种模式的区别

选项描述
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。
none不使用任何默认优化选项

如果没有设置,webpack 会给 mode 的默认值设置为 production

6.2 用法

在配置对象中设置 mode 选项:

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

或者从 CLI 参数中传递:

webpack --mode=development

7 source-map

我们的代码通常是通过打包压缩运行在浏览器上,也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的;

  • 比如 ES6 的代码可能被转换成 ES5;
  • 比如对应的代码行号、列号在经过编译后肯定会不一致;
  • 比如代码进行丑化压缩时,会将编码名称等修改;
  • 比如我们使用了 TypeScript 等方式编写的代码,最终转换成 JavaScript;

这样做会导致调试转换后的代码变得困难,我们能保证代码不出错吗?不可能。

那么如何可以调试这种转换后不一致的代码呢?

答案就是 source-map

source map 是一种文件格式,用于将压缩后的 javascript(或 css)代码的源代码映射回原始的、未压缩的文件中。 该文件提供了一个映射表,可使浏览器调试器能够让开发人员轻松地跟踪和调试代码。

7.1 如何使用source-map

如何可以使用 source-map 呢?两个步骤:

  1. 第一步:根据源文件,生成 source-map 文件,webpack 在打包时,可以通过配置生成 source-map ;
  2. 第二步:在转换后的代码,最后添加一个注释,它指向 sourcemap;
//# sourceMappingURL=common.bundle.js.map

浏览器会根据我们的注释,查找相应的 source-map,并且根据 source-map 还原我们的代码,方便进行调试。

Chrome 中,我们可以按照如下的方式打开 source-map:

Webpack入门教程(二):基础用法

7.2 分析 source-map

最初 source-map 生成的文件大小是原始文件的 10 倍,第二版减少了约50%,第三版又减少了 50%,所以目前一个 133kb 的文件, 最终的 source-map 的大小大概在 300kb

下图是一个简单的 source-map 文件

Webpack入门教程(二):基础用法

  1. version:当前使用的版本,也就是最新的第三版;
  2. `file: 打包后的文件(浏览器加载的文件);
  3. mappings:source-map 用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable-length quantity可变 长度值)编码;
  4. sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);
  5. sourceContent:转换前的具体代码信息(和sources是对应的关系);
  6. names:转换前的变量和属性名称
  7. sourceRoot:所有的sources相对的根目录;

7.3 生成 source-map

如何在使用 webpack 打包的时候,生成对应的 source-map 呢?

  • webpack 为我们提供了非常多的选项(目前是26个),来处理 source-map;
  • webpack.docschina.org/configurati…
  • 选择不同的值,生成的 source-map 会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择;

虽然 devtool 的选项多达 26 种,但是这么多种配置项其实只是下面七个关键字 evalsource-mapcheapmoduleinlinehiddennosources 的组合

关键字含义
source-map产生.map 文件
eval使用 eval 包裹模块代码
cheap不包含列信息,也不包含 loader 的 sourcemap
module包含 loader 的 sourcemap(比如 jsx to js ,babel 的 sourcemap),否则无法定义源文件
inline将.map 作为 DataURI 嵌入,不单独生成.map 文件
hidden会生成sourcemap,但是不会对source-map文件进行引用
nosources会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;

具体详解:

文件源码如下

console.log("hello wolrd");

1. source-map 处理后输出结果: 定位信息最全,但也 .map 文件最大,效率最低

console.log("hello wolrd");
//# sourceMappingURL=bundle.js.map

2. eval 处理输出结果:用eval 包裹源代码进行执行,利用字符串可缓存从而提效

(() => {
  var __webpack_modules__ = {
      138: () => {
        eval('console.log("hello wolrd");\n\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?');
      },
    },
    __webpack_exports__ = {};
  __webpack_modules__[138]();
})();

3. inline-source-map 处理输出结果inline会将 map 作为 DataURI 嵌入,不单独生成 .map 文件减少文件。

console.log("hello wolrd");
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwibWFwcGluZ3MiOiJBQUFBQSxRQUFRQyxJQUFJIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnNvbGUubG9nKFwiaGVsbG8gd29scmRcIik7XG4iXSwibmFtZXMiOlsiY29uc29sZSIsImxvZyJdLCJzb3VyY2VSb290IjoiIn0=

4. cheap-source-map 处理后输出结果cheap 的错误信息只会定义到行,而不会定义到列,精准度降低换取文件内容的缩小。

console.log("hello wolrd");
//# sourceMappingURL=bundle.js.map

cheap-source-mapsource-map的区别

Webpack入门教程(二):基础用法

5. cheap-module-source-map 处理后输出结果cheap-source-map相似,但是 cheap-module-source-map 对源自 loadersourcemap 处理会更好。

cheap-source-mapcheap-module-source-map的区别 Webpack入门教程(二):基础用法

6. hidden-source-map 处理后输出结果:会生成 sourcemap,但是不会对source-map 文件进行引用; 相当于删除了打包文件中对 sourcemap 的引用注释

// 被删除掉的
//# sourceMappingURL=bundle.js.map

如果我们手动添加进来,那么 sourcemap 就会生效了

7. hidden-source-map 处理后输出结果:会生成 sourcemap,但是生成的 sourcemap 只有错误信息的提示,不会生成源代码文件

正确的错误提示:

Webpack入门教程(二):基础用法

点击错误提示,无法查看源码:

Webpack入门教程(二):基础用法

上面我们讲了 webpack 给我门提供的26个值是有以上七个关键词组合的,他们的组合规则如下:

  • inline-|hidden-|eval:三个值时三选一;
  • nosources:可选值;
  • cheap可选值,并且可以跟随module的值;
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

那么在开发中,最佳的实践是什么呢?

  • 开发阶段:推荐使用 source-map 或者 cheap-module-source-map ✓ 这分别是 vuereact 使用的值,可以获取调试信息,方便快速开发;
  • 测试阶段:推荐使用 source-map 或者 cheap-module-source-map ✓ 测试阶段我们也希望在浏览器下看到正确的错误提示;
  • 发布阶段:false、缺省值(不写)

8 开发环境模式

目前我们开发的代码,为了运行需要有两个操作:

  1. 操作一:npm run build,编译相关的代码;
  2. 操作二:通过 live server 或者直接通过浏览器,打开 index.html 代码,查看效果;

这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成 编译展示;

为了完成自动编译,webpack 提供了几种可选的方式:

  1. webpack watch mode;
  2. webpack-dev-server(常用);
  3. webpack-dev-middleware;

8.1 使用 watch mode(观察模式)

我们可以指示 webpack "watch" 依赖图中所有文件的更改。如果其中一个文件被更新,代码将被重新编译,所以你不必再去手动运行整个构建。

  1. 我们添加一个用于启动 webpack watch modenpm scripts
   "scripts": {
    "watch": "webpack --watch",
   },
  1. 运行命令
npm run watch

现在只要当你更改了 ./src/index.js 中的代码,应该就可以看到 webpack 自动地重新编译修改后的模块

这个方法唯一的缺点是,为了看到修改后的实际效果,需要刷新浏览器。如果能够自动刷新浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。

8.2 使用 webpack-dev-server

webpack-dev-server 为我们提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。

  1. 安装依赖
npm install --save-dev webpack-dev-server
  1. 修改配置文件,告知 dev server,从什么位置查找文件:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: "./src/index.js",
  devtool: "source-map",
  mode: "development",
  devServer: {
    static: "./dist",
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Development",
    }),
  ],
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
};

以上配置告知 webpack-dev-server,将 dist 目录下的文件 servelocalhost:8080 下。

webpack-dev-server 会从 output.path 中定义的目录中的 bundle 文件提供服务,即文件将可以通过 http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename] 进行访问。

  1. 我们添加一个可以直接运行 dev serverscript
"start": "webpack serve --open",
  1. 现在,在命令行中运行 npm run start,我们会看到浏览器自动加载页面。如果你更改任何源文件并保存它们,web server 将在编译代码后自动重新加载。

Webpack入门教程(二):基础用法

webpack-dev-server 具有许多可配置的选项。关于其他更多配置,可以查看 官网配置文档

8.3 使用 webpack-dev-middleware

webpack-dev-middleware 是一中间件pack处理过的文件发送到一个server。事实上,webpack-dev-server在内部就是 wds + express 实现的,然而它也可以作为一个单独的package来使用,以便根据需求进行更多自定义设置。下面是一个webpack-dev-middleware配合express server` 的示例。

  1. 首先,安装 expresswebpack-dev-middleware
npm install --save-dev express webpack-dev-middleware
  1. 调整 webpack 配置文件,以确保 middleware(中间件) 功能能够正确启用
 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
   devServer: {
     static: './dist',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
+    publicPath: '/',
   },
 };
  1. 我们将会在 server 脚本使用 publicPath,以确保文件资源能够正确地 servehttp://localhost:3000 下,稍后我们会指定 port number(端口号)。接下来是设置自定义 express server
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);

// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
app.use(
  webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath,
  })
);

// 将文件 serve 到 port 3000。
app.listen(3000, function () {
  console.log('Example app listening on port 3000!\n');
});
  1. 添加一个 npm script,以使我们更方便地运行 server
"server": "node server.js",
  1. 现在,在 terminal(终端) 中执行 npm run server,将会有类似如下信息输出:

Webpack入门教程(二):基础用法

  1. 打开浏览器,访问 http://localhost:3000。应该看到 webpack 应用程序已经运行!