Skip to content

Commit

Permalink
feat: support passing query to loader (#4418)
Browse files Browse the repository at this point in the history
* feat: pass loader test `option`

* test: more test cases

* chore: cleanup

* test

* use correct version css-loader

---------

Co-authored-by: ahabhgk <ahabhgk@gmail.com>
  • Loading branch information
h-a-n-a and ahabhgk authored Oct 25, 2023
1 parent 4c2d3c0 commit 1a4d646
Show file tree
Hide file tree
Showing 36 changed files with 794 additions and 50 deletions.
2 changes: 2 additions & 0 deletions packages/rspack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@
"compare-versions": "6.0.0-rc.1",
"enhanced-resolve": "5.12.0",
"graceful-fs": "4.2.10",
"json-parse-even-better-errors": "^3.0.0",
"neo-async": "2.6.2",
"querystring": "^0.2.1",
"react-refresh": "0.14.0",
"schema-utils": "^4.0.0",
"tapable": "2.2.1",
Expand Down
68 changes: 46 additions & 22 deletions packages/rspack/src/loader-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,8 @@ function createLoaderObject(loader: any, compiler: Compiler): LoaderObject {
obj.path = splittedRequest.path;
obj.query = splittedRequest.query;
obj.fragment = splittedRequest.fragment;

if (obj.query.startsWith("??")) {
const ident = obj.query.slice(2);
if (ident === "[[missing ident]]") {
throw new Error(
"No ident is provided by referenced loader. " +
"When using a function for Rule.use in config you need to " +
"provide an 'ident' property for referenced loader options."
);
}
obj.options = compiler.ruleSet.references.get(ident);
if (obj.options === undefined) {
throw new Error("Invalid ident is provided by referenced loader");
}
obj.ident = ident;
} else {
obj.options = undefined;
obj.ident = undefined;
}
obj.options = undefined;
obj.ident = undefined;
} else {
if (!value.loader)
throw new Error(
Expand Down Expand Up @@ -182,9 +165,34 @@ export async function runLoaders(
const buildDependencies: string[] = rawContext.buildDependencies.slice();
const assetFilenames = rawContext.assetFilenames.slice();

const loaders = rawContext.currentLoader
.split("$")
.map(loader => createLoaderObject(loader, compiler));
const loaders = rawContext.currentLoader.split("$").map(loader => {
const splittedRequest = parsePathQueryFragment(loader);
const obj: any = {};
obj.loader = obj.path = splittedRequest.path;
obj.query = splittedRequest.query;
obj.fragment = splittedRequest.fragment;
const type = /\.mjs$/i.test(splittedRequest.path) ? "module" : "commonjs";
obj.type = type;
obj.options = splittedRequest.query
? splittedRequest.query.slice(1)
: undefined;
if (typeof obj.options === "string" && obj.options[0] === "?") {
const ident = obj.options.slice(1);
if (ident === "[[missing ident]]") {
throw new Error(
"No ident is provided by referenced loader. " +
"When using a function for Rule.use in config you need to " +
"provide an 'ident' property for referenced loader options."
);
}
obj.options = compiler.ruleSet.references.get(ident);
if (obj.options === undefined) {
throw new Error("Invalid ident is provided by referenced loader");
}
obj.ident = ident;
}
return createLoaderObject(obj, compiler);
});

loaderContext.__internal__context = rawContext;
loaderContext.context = contextDirectory;
Expand Down Expand Up @@ -504,6 +512,22 @@ export async function runLoaders(
const loader = getCurrentLoader(loaderContext);
let options = loader?.options;

if (typeof options === "string") {
if (options.startsWith("{") && options.endsWith("}")) {
try {
const parseJson = require("json-parse-even-better-errors");
options = parseJson(options);
} catch (e: any) {
throw new Error(`Cannot parse string options: ${e.message}`);
}
} else {
const querystring = require("querystring");
options = querystring.parse(options, "&", "=", {
maxKeys: 0
});
}
}

if (options === null || options === undefined) {
options = {};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let foo: string = "f1";
let bar: string = "b1";
let baz: string = "b2";
const boo = <string>"abc";
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require("./a");

it("should generate correct sourceMap", async () => {
const path = require("path");
const fs = require("fs");
const source = fs.readFileSync(__filename + ".map", "utf-8");
const map = JSON.parse(source);
const sourceContent = fs.readFileSync(
path.resolve(__dirname, "../a.ts"),
"utf-8"
);
expect(map.sources).toContain("./a.ts");
expect(map.sourcesContent[1]).toEqual(sourceContent);

checkStub("fo" + "o", sourceContent);
checkStub("ba" + "r", sourceContent);
checkStub("ba" + "z", sourceContent);
checkStub(wrap("f" + 1), sourceContent);
checkStub(wrap("b" + 1), sourceContent);
checkStub(wrap("b" + 2), sourceContent);
checkStub(wrap("ab" + "c"), sourceContent);
});

const wrap = v => `"${v}"`;
const checkStub = async (stub, sourceContent) => {
const fs = require("fs");
const { SourceMapConsumer } = require("source-map");

const source = fs.readFileSync(__filename + ".map", "utf-8");
const map = JSON.parse(source);
const consumer = await new SourceMapConsumer(map);
const generated = fs.readFileSync(__filename, "utf-8");
const { line, column } = consumer.originalPositionFor(
positionFor(generated, stub)
);
const { line: originalLine, column: originalColumn } = positionFor(
sourceContent,
stub
);
expect(line).toBe(originalLine);
expect(column).toBe(originalColumn);
};

const positionFor = (content, text) => {
let lines = content.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const column = lines[i].indexOf(text);
if (column >= 0) return { line: i + 1, column };
}
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
devtool: "source-map",
externals: ["source-map"],
externalsType: "commonjs",
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript"
}
}
}
}
],
type: "javascript/auto"
}
]
},
experiments: {
rspackFuture: {
disableTransformByDefault: true
}
}
};
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
45 changes: 45 additions & 0 deletions packages/rspack/tests/configCases/loader/options/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
it("should get options", function () {
expect(require("./a")).toStrictEqual({
arg: true,
arg1: null,
arg3: 1234567890,
arg4: "string",
arg5: [1, 2, 3],
arg6: { foo: "value", bar: { baz: "other-value" } }
});
expect(require("./b")).toStrictEqual({
arg: true,
arg1: null,
arg3: 1234567890,
arg4: "string",
arg5: [1, 2, 3],
arg6: { foo: "value", bar: { baz: "other-value" } }
});
expect(require("./c")).toStrictEqual({
arg: true,
arg1: null,
arg3: 1234567890,
arg4: "string",
arg5: [1, 2, 3],
arg6: { foo: "value", bar: { baz: "other-value" } }
});
expect(require("./d")).toStrictEqual({
arg4: "text"
});
expect(require("./e")).toStrictEqual({});
expect(require("./f")).toStrictEqual({
delicious: "",
name: "cheesecake",
slices: "8",
warm: "false"
});
expect(require("./g")).toStrictEqual({
"=": "="
});
expect(require("./h")).toStrictEqual({
foo: "bar"
});
expect(require("./i")).toStrictEqual({
foo: "bar"
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = [
/^Pack got invalid because of write to: Compilation\/modules.+loaders[/\\]options[/\\]error1\.js$/
];
12 changes: 12 additions & 0 deletions packages/rspack/tests/configCases/loader/options/loader-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const schema = require("./loader-1.options.json");

/** @type {import("@rspack/core").LoaderDefinition} */
module.exports = function () {
const options = this.getOptions(schema);

const json = JSON.stringify(options)
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");

return `module.exports = ${json}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"additionalProperties": false,
"properties": {
"arg": {
"type": "boolean"
},
"arg1": {
"type": "null"
},
"arg2": {},
"arg3": {
"type": "number"
},
"arg4": {
"type": "string"
},
"arg5": {
"type": "array",
"items": {
"type": "number"
}
},
"arg6": {
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "object",
"properties": {
"baz": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"type": "object"
}
12 changes: 12 additions & 0 deletions packages/rspack/tests/configCases/loader/options/loader-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const schema = require("./loader-2.options.json");

/** @type {import("@rspack/core").LoaderDefinition} */
module.exports = function () {
const options = this.getOptions(schema);

const json = JSON.stringify(options)
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");

return `module.exports = ${json}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"title": "Custom Loader Name configuration",
"additionalProperties": false,
"properties": {
"arg": {
"enum": [true]
}
},
"type": "object"
}
10 changes: 10 additions & 0 deletions packages/rspack/tests/configCases/loader/options/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import("@rspack/core").LoaderDefinition} */
module.exports = function () {
const options = this.getOptions();

const json = JSON.stringify(options)
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");

return `module.exports = ${json}`;
};
Loading

0 comments on commit 1a4d646

Please sign in to comment.