Tho*_*mas 3 typescript webpack ts-loader webpack-watch webpack-loader
我有一个简单的自定义 Webpack 加载器,它从.txt
文件生成 TypeScript 代码:
txt-loader.js
module.exports = function TxtLoader(txt) {
console.log(`TxtLoader invoked on ${this.resourcePath} with content ${JSON.stringify(txt)}`)
if (txt.indexOf('Hello') < 0) {
throw new Error(`No "Hello" found`)
}
return `export const TEXT: string = ${JSON.stringify(txt)}`
}
Run Code Online (Sandbox Code Playgroud)
在现实生活中,我会对输入进行一些解析;在此示例中,我们假设文件必须包含Hello
有效的文本。
该加载程序允许我导入文本文件,如下所示:
索引.ts
import { TEXT } from './hello.txt'
console.log(TEXT)
Run Code Online (Sandbox Code Playgroud)
一切都很好,除了一件事:(webpack watch
及其表弟webpack serve
)。第一个编译很好:
$ /tmp/webpack-loader-repro/node_modules/.bin/webpack watch
TxtLoader invoked on /tmp/webpack-loader-repro/hello.txt with content "Hello world!\n"
asset main.js 250 bytes [compared for emit] [minimized] (name: main)
./index.ts 114 bytes [built] [code generated]
./hello.txt 97 bytes [built] [code generated]
webpack 5.64.3 compiled successfully in 3952 ms
Run Code Online (Sandbox Code Playgroud)
但后来我更改了hello.txt
文件:
$ touch hello.txt
Run Code Online (Sandbox Code Playgroud)
突然奇怪的事情发生了:
TxtLoader invoked on /tmp/webpack-loader-repro/index.ts with content "import { TEXT } from './hello.txt'\n\nconsole.log(TEXT)\n"
TxtLoader invoked on /tmp/webpack-loader-repro/custom.d.ts with content "declare module '*.txt'\n"
[webpack-cli] Error: The loaded module contains errors
at /tmp/webpack-loader-repro/node_modules/webpack/lib/dependencies/LoaderPlugin.js:108:11
at /tmp/webpack-loader-repro/node_modules/webpack/lib/Compilation.js:1930:5
at /tmp/webpack-loader-repro/node_modules/webpack/lib/util/AsyncQueue.js:352:5
at Hook.eval [as callAsync] (eval at create (/tmp/webpack-loader-repro/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
at AsyncQueue._handleResult (/tmp/webpack-loader-repro/node_modules/webpack/lib/util/AsyncQueue.js:322:21)
at /tmp/webpack-loader-repro/node_modules/webpack/lib/util/AsyncQueue.js:305:11
at /tmp/webpack-loader-repro/node_modules/webpack/lib/Compilation.js:1392:15
at /tmp/webpack-loader-repro/node_modules/webpack/lib/HookWebpackError.js:68:3
at Hook.eval [as callAsync] (eval at create (/tmp/webpack-loader-repro/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
at Cache.store (/tmp/webpack-loader-repro/node_modules/webpack/lib/Cache.js:107:20)
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Run Code Online (Sandbox Code Playgroud)
看来 Webpack 决定向我的加载器抛出比配置中指定的文件更多的文件。
如果我删除加载器中抛出的异常并返回一些任意有效的 TypeScript 代码,则生成的代码main.js
看起来完全相同。所以看起来这些额外的操作完全是多余的。但我不认为正确的解决方案是让我的加载程序吞下这些异常。
加载器的配置如下:
webpack.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
},
{
test: /\.txt$/,
use: [
{
loader: 'ts-loader',
// Tell TypeScript that the input should be parsed as TypeScript,
// not JavaScript: </sf/answers/3314017451/>
options: { appendTsSuffixTo: [/\.txt$/] },
},
path.resolve('txt-loader.js'),
],
},
],
},
}
Run Code Online (Sandbox Code Playgroud)
最后,这些是将它们组合在一起的必要部分:
自定义.d.ts
declare module '*.txt'
Run Code Online (Sandbox Code Playgroud)
tsconfig.json
{}
Run Code Online (Sandbox Code Playgroud)
包.json
{
"name": "webpack-loader-repro",
"license": "MIT",
"private": true,
"devDependencies": {
"ts-loader": "9.2.6",
"typescript": "4.5.2",
"webpack": "5.64.3",
"webpack-cli": "4.9.1"
},
"dependencies": {}
}
Run Code Online (Sandbox Code Playgroud)
对于那些想在家尝试的人,请克隆这个最小的重现项目。
这是 Webpack 中的错误吗?在 ts-loader 中?在我的配置中?
主要问题是,ts-loader
将加载额外的文件并手动调用它们的加载程序。
在当前的 webpack 配置中,您最终将得到 2 个独立的ts-loader
实例:
.ts
文件.txt
文件在初始编译期间将发生以下情况:
index.ts
将由第一个ts-loader
实例处理,该实例将尝试编译它。ts-loader
不知道如何加载.txt
文件,因此它四处寻找一些模块声明并找到custom.d.ts
并加载它。ts-loader
知道如何处理.txt
文件,它将注册index.ts
并custom.d.ts
依赖于hello.txt
(addDependency
在此处调用)ts-loader
实例将要求 webpack 进行编译hello.txt
。hello.txt
将由第二个ts-loader
实例通过您的自定义加载器加载(就像人们所期望的那样)一旦你触摸(或修改)hello.txt
,webpack 将尽职尽责地通知所有hello.txt
已更改的观察者。但因为index.ts
&custom.d.ts
依赖于hello.txt
,所有观察者也会收到通知,这两者发生了变化。
第一个ts-loader
将获取所有 3 个更改事件,忽略一个hello.txt
,因为它没有编译该事件,并且对index.ts
&custom.d.ts
事件不执行任何操作,因为它发现没有更改。
第二个ts-loader
也将获取所有 3 个更改事件,如果您刚刚触摸它,它将忽略更改hello.txt
,或者在您编辑它时重新编译它。之后,它看到custom.d.ts
更改,意识到它尚未编译该更改,并将尝试编译它,同时调用其后指定的所有加载器。变化也会发生同样的事情index.ts
。
第二个甚至尝试加载这些文件的原因ts-loader
如下:
index.ts
:您.tsconfig
没有指定include
orexclude
或files
,因此将使用forts-loader
的默认值,即它可以找到的所有内容。因此,一旦它收到更改通知,它就会尝试加载它。
["**"]
include
index.ts
onlyCompileBundledFiles: true
- 因为在这种情况下ts-loader
意识到它应该忽略该文件。custom.d.ts
它基本上是相同的,但即使使用以下内容,它们仍然会包含在内onlyCompileBundledFiles: true
:
ts-loader 的默认行为是充当 tsc 命令的直接替代品,因此它尊重 tsconfig.json 中的 include、files 和 except 选项,加载这些选项指定的任何文件。onlyCompileBundledFiles 选项修改了此行为,仅加载那些实际由 webpack 捆绑的文件,以及 tsconfig.json 设置包含的任何 .d.ts 文件。.d.ts 文件仍然包含在内,因为它们可能需要在没有显式导入的情况下进行编译,因此不会被 webpack 拾取。
如果您修改txt-loader.js
为不抛出而是返回内容不变,即:
if (txt.indexOf('Hello') < 0) {
return txt;
}
Run Code Online (Sandbox Code Playgroud)
我们可以看到第三次、第四次等等......编译时发生了什么。
由于index.ts
&custom.d.ts
现在都在两个 s 的缓存中ts-loader
,因此只有在这些文件中的任何一个发生实际更改时才会调用您的自定义加载程序。
你不是唯一遇到这个“功能”的人,甚至还有一个开放的 github 问题:
有几种方法可以避免此问题:
.txt
ts-loader
只进行转译In transpileOnly: true
-modets-loader
将忽略所有其他文件,只处理 webpack 明确要求编译的文件。
所以这会起作用:
/* ... */
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
},
{
test: /\.txt$/,
use: [
{
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.txt$/], transpileOnly: true },
},
path.resolve('txt-loader.js'),
],
},
],
/* ... */
Run Code Online (Sandbox Code Playgroud)
.txt
尽管使用这种方法,您将失去对文件的类型检查。
ts-loader
实例只要为每个加载器指定完全相同的选项,ts-loader
就会重用该加载器实例。
这样您就拥有*.ts
文件和*.txt
文件的共享缓存,因此ts-loader
不会尝试*.ts
通过*.txt
webpack 规则传递文件。
所以下面的定义也可以工作:
/* ... */
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.txt$/] },
}
],
},
{
test: /\.txt$/,
use: [
{
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.txt$/] },
},
path.resolve('txt-loader.js'),
],
},
],
/* ... */
Run Code Online (Sandbox Code Playgroud)
ts-loader
'sinstance
选项ts-loader
有一个(相当隐藏的)instance
选项。
通常,这将用于隔离ts-loader
具有相同选项的两个实例 - 但它也可以用于强制合并两个ts-loader
实例。
所以这也可以工作:
/* ... */
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.txt$/], instance: "foobar" },
}
],
},
{
test: /\.txt$/,
use: [
{
loader: 'ts-loader',
options: { instance: "foobar", /* OTHER OPTIONS SILENTLY IGNORED */ },
},
path.resolve('txt-loader.js'),
],
},
],
/* ... */
Run Code Online (Sandbox Code Playgroud)
不过,您需要小心这个,因为由 webpack 实例化的第一个加载器将决定选项。ts-loader
您传递给所有其他具有相同选项的选项instance
将被默默地忽略。
*.ts
文件最简单的选择是将您的文件更改txt-loader.js
为不修改*.ts
文件,以防它被调用。这不是一个干净的解决方案,但它仍然有效:D
txt-loader.js
:
module.exports = function TxtLoader(txt) {
// ignore .ts files
if(this.resourcePath.endsWith('.ts'))
return txt;
// handle .txt files:
return `export const TEXT: string = ${JSON.stringify(txt)}`
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1497 次 |
最近记录: |