资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为 'javascript/auto' 来解决。
webpack.config.js
- module.exports = {
- module: {
- rules: [
- {
- test: /\.(png|jpg|gif)$/i,
- use: [
- {
- loader: 'url-loader',
- options: {
- limit: 8192,
- }
- },
- ],
- + type: 'javascript/auto'
- },
- ]
- },
- }
-
如需从 asset loader 中排除来自新 URL 处理的 asset,请添加 dependency: { not: ['url'] } 到 loader 配置中。
webpack.config.js
- module.exports = {
- module: {
- rules: [
- {
- test: /\.(png|jpg|gif)$/i,
- + dependency: { not: ['url'] },
- use: [
- {
- loader: 'url-loader',
- options: {
- limit: 8192,
- },
- },
- ],
- },
- ],
- }
- }
-
webpack.config.js
- const path = require('path');
-
- module.exports = {
- entry: './src/index.js',
- output: {
- filename: 'main.js',
- path: path.resolve(__dirname, 'dist')
- },
- + 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 来修改此模板字符串:
webpack.config.js
- 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 目录中。
Rule.generator.filename 与 output.assetModuleFilename 相同,并且仅适用于 asset 和 asset/resource 模块类型。
webpack.config.js
- 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: /\.svg/,
- + type: 'asset/inline'
- - },
- + }
- - {
- - test: /\.html/,
- - type: 'asset/resource',
- - generator: {
- - filename: 'static/[hash][ext][query]'
- - }
- - }
- ]
- }
- };
-
src/index.js
- - import mainImage from './images/main.png';
- + import metroMap from './images/metro.svg';
-
- - img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'
- + block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
-
所有 .svg 文件都将作为 data URI 注入到 bundle 中。
webpack 输出的 data URI,默认是呈现为使用 Base64 算法编码的文件内容。
如果要使用自定义编码算法,则可以指定一个自定义函数来编码文件内容:
webpack.config.js
- const path = require('path');
- + const svgToMiniDataURI = require('mini-svg-data-uri');
-
- module.exports = {
- entry: './src/index.js',
- output: {
- filename: 'main.js',
- path: path.resolve(__dirname, 'dist')
- },
- module: {
- rules: [
- {
- test: /\.svg/,
- type: 'asset/inline',
- + generator: {
- + dataUrl: content => {
- + content = content.toString();
- + return svgToMiniDataURI(content);
- + }
- + }
- }
- ]
- },
- };
-
现在,所有 .svg 文件都将通过 mini-svg-data-uri 包进行编码。
webpack.config.js
- const path = require('path');
- - const svgToMiniDataURI = require('mini-svg-data-uri');
-
- module.exports = {
- entry: './src/index.js',
- output: {
- filename: 'main.js',
- path: path.resolve(__dirname, 'dist')
- },
- module: {
- rules: [
- {
- - test: /\.svg/,
- - type: 'asset/inline',
- - generator: {
- - dataUrl: content => {
- - content = content.toString();
- - return svgToMiniDataURI(content);
- - }
- - }
- + test: /\.txt/,
- + type: 'asset/source',
- }
- ]
- },
- };
-
src/example.txt
- Hello world
-
src/index.js
- - import metroMap from './images/metro.svg';
- + import exampleText from './example.txt';
-
- - block.style.background = `url(${metroMap}); // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
- + block.textContent = exampleText; // 'Hello world'
-
所有 .txt 文件将原样注入到 bundle 中。
当使用 new URL('./path/to/asset', import.meta.url),webpack 也会创建资源模块。
src/index.js
- const logo = new URL('./logo.svg', import.meta.url);
-
根据你配置中 target 的不同,webpack 会将上述代码编译成不同结果:
- // target: web
- new URL(
- __webpack_public_path__ + 'logo.svg',
- document.baseURI || self.location.href
- );
-
- // target: webworker
- new URL(__webpack_public_path__ + 'logo.svg', self.location);
-
- // target: node, node-webkit, nwjs, electron-main, electron-renderer, electron-preload, async-node
- new URL(
- __webpack_public_path__ + 'logo.svg',
- require('url').pathToFileUrl(__filename)
- );
-
webpack.config.js
- const path = require('path');
-
- module.exports = {
- entry: './src/index.js',
- output: {
- filename: 'main.js',
- path: path.resolve(__dirname, 'dist')
- },
- module: {
- rules: [
- {
- + test: /\.txt/,
- + type: 'asset',
- }
- ]
- },
- };
-
现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:
webpack.config.js
- const path = require('path');
-
- 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
- + }
- + }
- }
- ]
- },
- };
-
还可以 指定一个函数 来决定是否 inline 模块。
在 asset 模块和 webpack 5 之前,可以使用内联语法与上述传统的 loader 结合使用。
现在建议去掉所有的 loader 的语法,使用资源查询条件来魔法内联语法的功能。
示例,将 raw-loader 替换为 asset/source 类型:
- - import myModule from 'raw-loader!my-module';
- + import myModule from 'my-module?raw';
-
webpack 相关配置:
- module: {
- rules: [
- // ...
- + {
- + resouceQuery: /raw/
- + type: 'asset/source'
- + }
- ]
- },
-
如果你想把原始资源排除在其他 loader 的解析范围以外,请使用取反的符合:
- module: {
- rules: [
- // ...
- + {
- + test: /\.m?js$/,
- + resourceQuery: /^(?!raw$).*/,
- + },
- {
- resouceQuery: /raw/
- type: 'asset/source'
- }
- ]
- },
-
在不使用 import 样式文件的应用程序中(预单页应用程序或其他原因),使用一个值数组结构的 entry,并且在其中传入不同类型的文件,可以实现将 CSS 和 JavaScript(和其他)文件分离在不同的 bundle。
举个例子。我们有一个具有两种页面类型的 PHP 应用程序:home(首页) 和 account(帐户)。home 与应用程序其余部分(account 页面)具有不同的布局和不可共享的 JavaScript。我们想要从应用程序文件中输出 home 页面的 home.js 和 home.css,为 account 页面输出 account.js 和 account.css。
home.js
- console.log('home page type');
-
home.scss
- // home page individual styles
-
account.js
- console.log('account page type');
-
account.scss
- // account page individual styles
-
我们将在 production(生产) 模式中使用 MiniCssExtractPlugin 作为 CSS 的一个最佳实践。
webpack.config.js
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-
- module.exports = {
- mode: process.env.NODE_ENV,
- entry: {
- home: ['./home.js', './home.scss'],
- account: ['./account.js', './account.scss'],
- },
- output: {
- filename: '[name].js',
- },
- module: {
- rules: [
- {
- test: /\.scss$/,
- use: [
- // fallback to style-loader in development
- process.env.NODE_ENV !== 'production'
- ? 'style-loader'
- : MiniCssExtractPlugin.loader,
- 'css-loader',
- 'sass-loader',
- ],
- },
- ],
- },
- plugins: [
- new MiniCssExtractPlugin({
- filename: '[name].css',
- }),
- ],
- };
-
由于我们未指定其他输出路径,因此使用以上配置运行 webpack 将输出到 ./dist。./dist 目录下现在包含四个文件:
TypeScript 是 JavaScript 的超集,为其增加了类型系统,可以编译为普通 JavaScript 代码。这篇指南里我们将会学习是如何将 webpack 和 TypeScript 进行集成。
首先,执行以下命令安装 TypeScript compiler 和 loader:
- npm install --save-dev typescript ts-loader
-
现在,我们将修改目录结构和配置文件:
project
- webpack-demo
- |- package.json
- + |- tsconfig.json
- |- webpack.config.js
- |- /dist
- |- bundle.js
- |- index.html
- |- /src
- |- index.js
- + |- index.ts
- |- /node_modules
-
tsconfig.json
这里我们设置一个基本的配置,来支持 JSX,并将 TypeScript 编译到 ES5……
- {
- "compilerOptions": {
- "outDir": "./dist/",
- "noImplicitAny": true,
- "module": "es6",
- "target": "es5",
- "jsx": "react",
- "allowJs": true
- }
- }
-
查看 TypeScript 官方文档 了解更多关于 tsconfig.json 的配置选项。
想要了解 webpack 配置的更多信息,请查看 配置 概念。
现在,配置 webpack 处理 TypeScript:
webpack.config.js
- 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'],
- },
- output: {
- filename: 'bundle.js',
- path: path.resolve(__dirname, 'dist'),
- },
- };
-
这会让 webpack 直接从 ./index.ts 进入,然后通过 ts-loader _加载_所有的 .ts 和 .tsx 文件,并且在当前目录_输出_一个 bundle.js 文件。
现在让我们改变 lodash 在 ./index.ts 文件中的引入, 因为在 lodash 的定义中没有默认(default)的导出。
./index.ts
- - import _ from 'lodash';
- + import * as _ from 'lodash';
-
- function component() {
- const element = document.createElement('div');
-
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-
- return element;
- }
-
- document.body.appendChild(component());
-
Tip
如果想在 TypeScript 中保留如import _ from 'lodash';的语法被让它作为一种默认的导入方式,需要在文件 tsconfig.json 中设置 "allowSyntheticDefaultImports" : true 和 "esModuleInterop" : true 。这个是与 TypeScript 相关的配置,在本文档中提及仅供参考。
在本指南中,我们使用 ts-loader,因为它能够很方便地启用额外的 webpack 功能,例如将其他 web 资源导入到项目中。
Warning
ts-loader使用tscTypeScript编译器,并取决于您的tsconfig.json配置。确保避免设置module为“ CommonJS”,否则webpack将无法摇晃您的代码。
请注意,如果您已经在使用babel-loader代码转译,则可以使用@babel/preset-typescriptBabel并让其处理JavaScript和TypeScript文件,而无需使用其他加载器。请记住,与相反ts-loader,底层@babel/plugin-transform-typescript插件不执行任何类型检查。
想要了解 source map 的更多信息,请查看 开发 指南。
想要启用 source map,我们必须配置 TypeScript,以将内联的 source map 输出到编译后的 JavaScript 文件中。必须在 TypeScript 配置中添加下面这行:
tsconfig.json
- {
- "compilerOptions": {
- "outDir": "./dist/",
- + "sourceMap": true,
- "noImplicitAny": true,
- "module": "commonjs",
- "target": "es5",
- "jsx": "react",
- "allowJs": true
- }
- }
-
现在,我们需要告诉 webpack 提取这些 source map,并内联到最终的 bundle 中。
webpack.config.js
- const path = require('path');
-
- module.exports = {
- entry: './src/index.ts',
- + devtool: 'inline-source-map',
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- use: 'ts-loader',
- exclude: /node_modules/,
- },
- ],
- },
- resolve: {
- extensions: [ '.tsx', '.ts', '.js' ],
- },
- output: {
- filename: 'bundle.js',
- path: path.resolve(__dirname, 'dist'),
- },
- };
-
查看 devtool 文档以了解更多信息。
在从 npm 安装 third party library(第三方库) 时,一定要记得同时安装此 library 的类型声明文件(typing definition)。你可以从 TypeSearch 中找到并安装这些第三方库的类型声明文件。
举个例子,如果想安装 lodash 类型声明文件,我们可以运行下面的命令:
- npm install --save-dev @types/lodash
-
想了解更多,可以查看 这篇文章。
想要在 TypeScript 中使用非代码资源(non-code asset),我们需要告诉 TypeScript 推断导入资源的类型。在项目里创建一个 custom.d.ts 文件,这个文件用来表示项目中 TypeScript 的自定义类型声明。我们为 .svg 文件设置一个声明:
custom.d.ts
- declare module '*.svg' {
- const content: any;
- export default content;
- }
-
H这里,我们通过指定任何以 .svg 结尾的导入(import),将 SVG 声明(declare) 为一个新的模块(module),并将模块的 content 定义为 any。我们可以通过将类型定义为字符串,来更加显式地将它声明为一个 url。同样的概念适用于其他资源,包括 CSS, SCSS, JSON 等。
Warning
这可能会降低构建性能。