aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/builtins/js
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-04-25 07:27:18 -0700
committerGravatar GitHub <noreply@github.com> 2023-04-25 07:27:18 -0700
commit126885e1fe509b69be947d79aacb3ed6efdf666a (patch)
treebef407938525d69132824ffd3b0b54796035009d /src/bun.js/builtins/js
parent5353d4101493632cb25d0cdddfef94f62bc5902d (diff)
downloadbun-126885e1fe509b69be947d79aacb3ed6efdf666a.tar.gz
bun-126885e1fe509b69be947d79aacb3ed6efdf666a.tar.zst
bun-126885e1fe509b69be947d79aacb3ed6efdf666a.zip
Implement `onResolve` plugins in `Bun.build()`, support multiple onLoad and onResolve plugins (#2739)
* its 2023 * WIP `onResolve` plugins * more progress * it compiles * Lots of small fixes * Seems to work excluding entry points * Update BundlerPluginBuiltins.cpp --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/builtins/js')
-rw-r--r--src/bun.js/builtins/js/BundlerPlugin.js418
-rw-r--r--src/bun.js/builtins/js/ConsoleObject.js2
-rw-r--r--src/bun.js/builtins/js/ImportMetaObject.js2
-rw-r--r--src/bun.js/builtins/js/JSBufferConstructor.js2
-rw-r--r--src/bun.js/builtins/js/JSBufferPrototype.js2
-rw-r--r--src/bun.js/builtins/js/ProcessObjectInternals.js2
6 files changed, 423 insertions, 5 deletions
diff --git a/src/bun.js/builtins/js/BundlerPlugin.js b/src/bun.js/builtins/js/BundlerPlugin.js
new file mode 100644
index 000000000..70e6a97a5
--- /dev/null
+++ b/src/bun.js/builtins/js/BundlerPlugin.js
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2023 Codeblog Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This API expects 4 functions:
+// - onLoadAsync
+// - onResolveAsync
+// - addError
+// - addFilter
+//
+// It should be generic enough to reuse for Bun.plugin() eventually, too.
+
+function runOnResolvePlugins(
+ specifier,
+ inputNamespace,
+ importer,
+ internalID,
+ kindId
+) {
+ "use strict";
+
+ // Must be kept in sync with ImportRecord.label
+ const kind = [
+ "entry-point",
+ "import-statement",
+ "require-call",
+ "dynamic-import",
+ "require-resolve",
+ "import-rule",
+ "url-token",
+ "internal",
+ ][kindId];
+
+ var promiseResult = (async (inputPath, inputNamespace, importer, kind) => {
+ var results = this.onResolve.@get(inputNamespace);
+ if (!resuls) {
+ this.onResolveAsync(internalID, null, null, null);
+ return null;
+ }
+
+ for (let [filter, callback] of results) {
+ if (filtertest(inputPath)) {
+ var result = callback({
+ path: inputPath,
+ importer,
+ namespace: inputNamespace,
+ kind,
+ });
+ while (
+ result &&
+ @isPromise(result) &&
+ (@getPromiseInternalField(result, @promiseFieldFlags) &
+ @promiseStateMask) ===
+ @promiseStateFulfilled
+ ) {
+ result = @getPromiseInternalField(
+ result,
+ @promiseFieldReactionsOrResult
+ );
+ }
+
+ if (result && @isPromise(result)) {
+ result = await result;
+ }
+
+ if (!result || !@isObject(result)) {
+ continue;
+ }
+
+ var {
+ path,
+ namespace: userNamespace = inputNamespace,
+ external,
+ } = result;
+ if (
+ !(typeof path === "string") ||
+ !(typeof userNamespace === "string")
+ ) {
+ @throwTypeError(
+ "onResolve plugins must return an object with a string 'path' and string 'loader' field"
+ );
+ }
+
+ if (!path) {
+ continue;
+ }
+
+ if (!userNamespace) {
+ userNamespace = inputNamespace;
+ }
+
+ if (typeof external !== "boolean" && !@isUndefinedOrNull(external)) {
+ @throwTypeError(
+ 'onResolve plugins "external" field must be boolean or unspecified'
+ );
+ }
+
+ if (!external) {
+ if (userNamespace === "file") {
+ // TODO: Windows
+ if (path[0] !== "/" || path.includes("..")) {
+ @throwTypeError(
+ 'onResolve plugin "path" must be absolute when the namespace is "file"'
+ );
+ }
+ }
+
+ if (userNamespace === "dataurl") {
+ if (!path.startsWith("data:")) {
+ @throwTypeError(
+ 'onResolve plugin "path" must start with "data:" when the namespace is"dataurl"'
+ );
+ }
+ }
+ }
+
+ this.onReslveAsync(internalID, path, userNamespace, external);
+ return null;
+ }
+ }
+
+ this.onResolveAsync(internalID, null, null, null);
+ return null;
+ })(specifier, inputNamespace, importer, kind);
+
+ while (
+ promiseResult &&
+ @isPromise(promiseResult) &&
+ (@getPromiseInternalField(promiseResult, @promiseFieldFlags) &
+ @promiseStateMask) ===
+ @promiseStateFulfilled
+ ) {
+ promiseResult = @getPromiseInternalField(
+ promiseResult,
+ @promiseFieldReactionsOrResult
+ );
+ }
+
+ if (promiseResult && @isPromise(promiseResult)) {
+ promiseResult.then(
+ () => {},
+ (e) => {
+ this.addError(internalID, e, 0);
+ }
+ );
+ }
+}
+
+function runSetupFunction(setup) {
+ "use strict";
+ var onLoadPlugins = new Map(),
+ onResolvePlugins = new Map();
+
+ function validate(filterObject, callback, map) {
+ if (!filterObject || !@isObject(filterObject)) {
+ @throwTypeError('Expected an object with "filter" RegExp');
+ }
+
+ if (!callback || !@isCallable(callback)) {
+ @throwTypeError("callback must be a function");
+ }
+
+ var { filter, namespace = "file" } = filterObject;
+
+ if (!filter) {
+ @throwTypeError('Expected an object with "filter" RegExp');
+ }
+
+ if (!@isRegExpObject(filter)) {
+ @throwTypeError("filter must be a RegExp");
+ }
+
+ if (namespace && !(typeof namespace === "string")) {
+ @throwTypeError("namespace must be a string");
+ }
+
+ if (namespace?.length ?? 0) {
+ namespace = "file";
+ }
+
+ if (!/^([/@a-zA-Z0-9_\\-]+)$/.test(namespace)) {
+ @throwTypeError("namespace can only contain @a-zA-Z0-9_\\-");
+ }
+
+ var callbacks = map.@get(namespace);
+
+ if (!callbacks) {
+ map.@set(namespace, [[filter, callback]]);
+ } else {
+ @arrayPush(callbacks, [filter, callback]);
+ }
+ }
+
+ function onLoad(filterObject, callback) {
+ validate(filterObject, callback, onLoadPlugins);
+ }
+
+ function onResolve(filterObject, callback) {
+ validate(filterObject, callback, onResolvePlugins);
+ }
+
+ const processSetupResult = () => {
+ var anyOnLoad = false,
+ anyOnResolve = false;
+
+ for (var [namespace, callbacks] of onLoadPlugins.entries()) {
+ for (var [filter] of callbacks) {
+ this.addFilter(filter, namespace, 1);
+ anyOnLoad = true;
+ }
+ }
+
+ for (var [namespace, callbacks] of onResolvePlugins.entries()) {
+ for (var [filter] of callbacks) {
+ this.addFilter(filter, namespace, 0);
+ anyOnResolve = true;
+ }
+ }
+
+ if (anyOnResolve) {
+ var onResolveObject = this.onResolve;
+ if (!onResolveObject) {
+ this.onResolve = onResolvePlugins;
+ } else {
+ for (var [namespace, callbacks] of onResolvePlugins.entries()) {
+ var existing = onResolveObject.@get(namespace);
+
+ if (!existing) {
+ onResolveObject.@set(namespace, callbacks);
+ } else {
+ onResolveObject.@set(existing.concat(callbacks));
+ }
+ }
+ }
+ }
+
+ if (anyOnLoad) {
+ var onLoadObject = this.onLoad;
+ if (!onLoadObject) {
+ this.onLoad = onLoadPlugins;
+ } else {
+ for (var [namespace, callbacks] of onLoadPlugins.entries()) {
+ var existing = onLoadObject.@get(namespace);
+
+ if (!existing) {
+ onLoadObject.@set(namespace, callbacks);
+ } else {
+ onLoadObject.@set(existing.concat(callbacks));
+ }
+ }
+ }
+ }
+
+ return anyOnLoad || anyOnResolve;
+ };
+
+ var setupResult = setup({
+ onLoad,
+ onResolve,
+ });
+
+ if (setupResult && @isPromise(setupResult)) {
+ if (
+ @getPromiseInternalField(setupResult, @promiseFieldFlags) &
+ @promiseStateFulfilled
+ ) {
+ setupResult = @getPromiseInternalField(
+ setupResult,
+ @promiseFieldReactionsOrResult
+ );
+ } else {
+ return setupResult.@then(processSetupResult);
+ }
+ }
+
+ return processSetupResult();
+}
+
+function runOnLoadPlugins(internalID, path, namespace, defaultLoaderId) {
+ "use strict";
+
+ const LOADERS_MAP = {
+ jsx: 0,
+ js: 1,
+ ts: 2,
+ tsx: 3,
+ css: 4,
+ file: 5,
+ json: 6,
+ toml: 7,
+ wasm: 8,
+ napi: 9,
+ base64: 10,
+ dataurl: 11,
+ text: 12,
+ };
+ const loaderName = [
+ "jsx",
+ "js",
+ "ts",
+ "tsx",
+ "css",
+ "file",
+ "json",
+ "toml",
+ "wasm",
+ "napi",
+ "base64",
+ "dataurl",
+ "text",
+ ][defaultLoaderId];
+
+ var promiseResult = (async (internalID, path, namespace, defaultLoader) => {
+ var results = this.onLoad.@get(namespace);
+ if (!results) {
+ this.onLoadAsync(internalID, null, null, null);
+ return null;
+ }
+
+ for (let [filter, callback] of results) {
+ if (filter.test(path)) {
+ var result = callback({
+ path,
+ namespace,
+ loader: defaultLoader,
+ });
+
+ while (
+ result &&
+ @isPromise(result) &&
+ (@getPromiseInternalField(result, @promiseFieldFlags) &
+ @promiseStateMask) ===
+ @promiseStateFulfilled
+ ) {
+ result = @getPromiseInternalField(
+ result,
+ @promiseFieldReactionsOrResult
+ );
+ }
+
+ if (result && @isPromise(result)) {
+ result = await result;
+ }
+
+ if (!result || !@isObject(result)) {
+ continue;
+ }
+
+ var { contents, loader = defaultLoader } = result;
+ if (!(typeof contents === "string") && !@isTypedArrayView(contents)) {
+ @throwTypeError(
+ 'onLoad plugins must return an object with "contents" as a string or Uint8Array'
+ );
+ }
+
+ if (!(typeof loader === "string")) {
+ @throwTypeError(
+ 'onLoad plugins must return an object with "loader" as a string'
+ );
+ }
+
+ const chosenLoader = LOADERS_MAP[loader];
+ if (chosenLoader === @undefined) {
+ @throwTypeError('Loader "' + loader + '" is not supported.');
+ }
+
+ this.onLoadAsync(internalID, contents, chosenLoader);
+ return null;
+ }
+ }
+
+ this.onLoadAsync(internalID, null, null);
+ return null;
+ })(internalID, path, namespace, loaderName);
+
+ while (
+ promiseResult &&
+ @isPromise(promiseResult) &&
+ (@getPromiseInternalField(promiseResult, @promiseFieldFlags) &
+ @promiseStateMask) ===
+ @promiseStateFulfilled
+ ) {
+ promiseResult = @getPromiseInternalField(
+ promiseResult,
+ @promiseFieldReactionsOrResult
+ );
+ }
+
+ if (promiseResult && @isPromise(promiseResult)) {
+ promiseResult.then(
+ () => {},
+ (e) => {
+ this.addError(internalID, e, 0);
+ }
+ );
+ }
+}
diff --git a/src/bun.js/builtins/js/ConsoleObject.js b/src/bun.js/builtins/js/ConsoleObject.js
index 8601748c6..d51ed08ef 100644
--- a/src/bun.js/builtins/js/ConsoleObject.js
+++ b/src/bun.js/builtins/js/ConsoleObject.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Codeblog Corp. All rights reserved.
+ * Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/src/bun.js/builtins/js/ImportMetaObject.js b/src/bun.js/builtins/js/ImportMetaObject.js
index b8d7900c0..e2a9a42d8 100644
--- a/src/bun.js/builtins/js/ImportMetaObject.js
+++ b/src/bun.js/builtins/js/ImportMetaObject.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Codeblog Corp. All rights reserved.
+ * Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/src/bun.js/builtins/js/JSBufferConstructor.js b/src/bun.js/builtins/js/JSBufferConstructor.js
index eecd52568..f5389385e 100644
--- a/src/bun.js/builtins/js/JSBufferConstructor.js
+++ b/src/bun.js/builtins/js/JSBufferConstructor.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Codeblog Corp. All rights reserved.
+ * Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/src/bun.js/builtins/js/JSBufferPrototype.js b/src/bun.js/builtins/js/JSBufferPrototype.js
index 32aebb3f2..ec22806e2 100644
--- a/src/bun.js/builtins/js/JSBufferPrototype.js
+++ b/src/bun.js/builtins/js/JSBufferPrototype.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Codeblog Corp. All rights reserved.
+ * Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/src/bun.js/builtins/js/ProcessObjectInternals.js b/src/bun.js/builtins/js/ProcessObjectInternals.js
index d9ab6d88a..82df97f63 100644
--- a/src/bun.js/builtins/js/ProcessObjectInternals.js
+++ b/src/bun.js/builtins/js/ProcessObjectInternals.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Codeblog Corp. All rights reserved.
+ * Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions