diff --git a/CHANGELOG.md b/CHANGELOG.md index b8e7902d57..05f8f7754a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 29.7.0 + +- Export jsenvPluginPlaceholders + # 29.6.1 - Fix error introduced in 29.6.0 on dev server diff --git a/packages/jsenv-plugin-react/package.json b/packages/jsenv-plugin-react/package.json index 711324141a..155d5c8e00 100644 --- a/packages/jsenv-plugin-react/package.json +++ b/packages/jsenv-plugin-react/package.json @@ -1,6 +1,6 @@ { "name": "@jsenv/plugin-react", - "version": "1.1.5", + "version": "1.1.6", "license": "MIT", "repository": { "type": "git", diff --git a/packages/jsenv-plugin-react/src/jsenv_plugin_react.js b/packages/jsenv-plugin-react/src/jsenv_plugin_react.js index c92c7288e2..0a92350c76 100644 --- a/packages/jsenv-plugin-react/src/jsenv_plugin_react.js +++ b/packages/jsenv-plugin-react/src/jsenv_plugin_react.js @@ -24,6 +24,10 @@ export const jsenvPluginReact = ({ "/**/node_modules/react/jsx-runtime/": { external: ["react"] }, "/**/node_modules/react/jsx-dev-runtime": { external: ["react"] }, "/**/react-refresh/": { external: ["react"] }, + // in case redux is used + "/**/node_modules/react-is/": true, + "/**/node_modules/use-sync-external-store/": { external: ["react"] }, + "/**/node_modules/hoist-non-react-statics/": { external: ["react-is"] }, }, }), jsenvPluginReactRefreshPreamble(), diff --git a/packages/urls/package.json b/packages/urls/package.json index 14ca3a9915..c4e5b197d3 100644 --- a/packages/urls/package.json +++ b/packages/urls/package.json @@ -1,6 +1,6 @@ { "name": "@jsenv/urls", - "version": "1.2.7", + "version": "1.2.8", "license": "MIT", "repository": { "type": "git", diff --git a/packages/urls/src/url_utils.js b/packages/urls/src/url_utils.js index 58edc77dac..dae9d6b396 100644 --- a/packages/urls/src/url_utils.js +++ b/packages/urls/src/url_utils.js @@ -3,9 +3,12 @@ import { urlToExtension } from "./url_to_extension.js" import { urlToResource } from "./url_to_resource.js" export const asUrlWithoutSearch = (url) => { - const urlObject = new URL(url) - urlObject.search = "" - return urlObject.href + if (url.includes("?")) { + const urlObject = new URL(url) + urlObject.search = "" + return urlObject.href + } + return url } export const isValidUrl = (url) => { diff --git a/src/main.js b/src/main.js index 203b592deb..7fd827f519 100644 --- a/src/main.js +++ b/src/main.js @@ -23,8 +23,8 @@ export { startBuildServer } from "./build/start_build_server.js" // helpers export { pingServer } from "./ping_server.js" -export { replacePlaceholders } from "./helpers/replace_placeholders.js" // advanced -export { execute } from "./execute/execute.js" export { jsenvPluginInjectGlobals } from "./plugins/inject_globals/jsenv_plugin_inject_globals.js" +export { jsenvPluginPlaceholders } from "./plugins/placeholders/jsenv_plugin_placeholders.js" +export { execute } from "./execute/execute.js" diff --git a/src/plugins/inject_globals/jsenv_plugin_inject_globals.js b/src/plugins/inject_globals/jsenv_plugin_inject_globals.js index 86c3308bb8..f29214326a 100644 --- a/src/plugins/inject_globals/jsenv_plugin_inject_globals.js +++ b/src/plugins/inject_globals/jsenv_plugin_inject_globals.js @@ -1,21 +1,33 @@ +import { URL_META } from "@jsenv/url-meta" +import { asUrlWithoutSearch } from "@jsenv/urls" + import { injectGlobals } from "./inject_globals.js" -export const jsenvPluginInjectGlobals = (urlAssociations) => { +export const jsenvPluginInjectGlobals = (rawAssociations) => { + let resolvedAssociations + return { name: "jsenv:inject_globals", appliesDuring: "*", + init: (context) => { + resolvedAssociations = URL_META.resolveAssociations( + { injector: rawAssociations }, + context.rootDirectoryUrl, + ) + }, transformUrlContent: async (urlInfo, context) => { - const url = Object.keys(urlAssociations).find((url) => { - return url === urlInfo.url + const { injector } = URL_META.applyAssociations({ + url: asUrlWithoutSearch(urlInfo.url), + associations: resolvedAssociations, }) - if (!url) { + if (!injector) { return null } - let globals = urlAssociations[url] - if (typeof globals === "function") { - globals = await globals(urlInfo, context) + if (typeof injector !== "function") { + throw new TypeError("injector must be a function") } - if (Object.keys(globals).length === 0) { + const globals = await injector(urlInfo, context) + if (!globals || Object.keys(globals).length === 0) { return null } return injectGlobals(urlInfo, globals) diff --git a/src/plugins/minification/jsenv_plugin_minification.js b/src/plugins/minification/jsenv_plugin_minification.js index a16b5c695e..e2670ab7b9 100644 --- a/src/plugins/minification/jsenv_plugin_minification.js +++ b/src/plugins/minification/jsenv_plugin_minification.js @@ -37,6 +37,30 @@ export const jsenvPluginMinification = (minification) => { options: minification.json, }) : null + const cssOptimizer = minification.css + ? (urlInfo, context) => + minifyCss({ + cssUrlInfo: urlInfo, + context, + options: minification.css, + }) + : null + const jsClassicOptimizer = minification.js_classic + ? (urlInfo, context) => + minifyJs({ + jsUrlInfo: urlInfo, + context, + options: minification.js_classic, + }) + : null + const jsModuleOptimizer = minification.js_module + ? (urlInfo, context) => + minifyJs({ + jsUrlInfo: urlInfo, + context, + options: minification.js_module, + }) + : null return { name: "jsenv:minification", @@ -44,30 +68,9 @@ export const jsenvPluginMinification = (minification) => { optimizeUrlContent: { html: htmlOptimizer, svg: htmlOptimizer, - css: minification.css - ? (urlInfo, context) => - minifyCss({ - cssUrlInfo: urlInfo, - context, - options: minification.css, - }) - : null, - js_classic: minification.js_classic - ? (urlInfo, context) => - minifyJs({ - jsUrlInfo: urlInfo, - context, - options: minification.js_classic, - }) - : null, - js_module: minification.js_module - ? (urlInfo, context) => - minifyJs({ - jsUrlInfo: urlInfo, - context, - options: minification.js_module, - }) - : null, + css: cssOptimizer, + js_classic: jsClassicOptimizer, + js_module: jsModuleOptimizer, json: jsonOptimizer, importmap: jsonOptimizer, webmanifest: jsonOptimizer, diff --git a/src/plugins/placeholders/jsenv_plugin_placeholders.js b/src/plugins/placeholders/jsenv_plugin_placeholders.js new file mode 100644 index 0000000000..9a17a87bb7 --- /dev/null +++ b/src/plugins/placeholders/jsenv_plugin_placeholders.js @@ -0,0 +1,36 @@ +import { URL_META } from "@jsenv/url-meta" +import { asUrlWithoutSearch } from "@jsenv/urls" + +import { replacePlaceholders } from "./replace_placeholders.js" + +export const jsenvPluginPlaceholders = (rawAssociations) => { + let resolvedAssociations + + return { + name: "jsenv:placeholders", + appliesDuring: "*", + init: (context) => { + resolvedAssociations = URL_META.resolveAssociations( + { replacer: rawAssociations }, + context.rootDirectoryUrl, + ) + }, + transformUrlContent: async (urlInfo, context) => { + const { replacer } = URL_META.applyAssociations({ + url: asUrlWithoutSearch(urlInfo.url), + associations: resolvedAssociations, + }) + if (!replacer) { + return null + } + if (typeof replacer !== "function") { + throw new TypeError("replacer must be a function") + } + const replacements = await replacer(urlInfo, context) + if (!replacements || Object.keys(replacements).length === 0) { + return null + } + return replacePlaceholders(urlInfo, replacements) + }, + } +} diff --git a/src/helpers/replace_placeholders.js b/src/plugins/placeholders/replace_placeholders.js similarity index 51% rename from src/helpers/replace_placeholders.js rename to src/plugins/placeholders/replace_placeholders.js index 7c8e3c31ed..fbacd1ec8b 100644 --- a/src/helpers/replace_placeholders.js +++ b/src/plugins/placeholders/replace_placeholders.js @@ -1,13 +1,21 @@ import { createMagicSource } from "@jsenv/sourcemap" -export const replacePlaceholders = (content, replacements) => { +export const replacePlaceholders = (urlInfo, replacements) => { + const content = urlInfo.content const magicSource = createMagicSource(content) Object.keys(replacements).forEach((key) => { let index = content.indexOf(key) while (index !== -1) { const start = index const end = index + key.length - magicSource.replace({ start, end, replacement: replacements[key] }) + magicSource.replace({ + start, + end, + replacement: + urlInfo.type === "js_classic" || urlInfo.type === "js_module" + ? JSON.stringify(replacements[key], null, " ") + : replacements[key], + }) index = content.indexOf(key, end) } }) diff --git a/src/plugins/url_version/jsenv_plugin_url_version.js b/src/plugins/url_version/jsenv_plugin_url_version.js index f508fa23ae..1a6d43a456 100644 --- a/src/plugins/url_version/jsenv_plugin_url_version.js +++ b/src/plugins/url_version/jsenv_plugin_url_version.js @@ -1,7 +1,7 @@ export const jsenvPluginUrlVersion = () => { return { name: "jsenv:url_version", - appliesDuring: "*", + appliesDuring: "dev", redirectUrl: (reference) => { // "v" search param goal is to enable long-term cache // for server response headers diff --git a/tests/__internal__/replace_placerholders.test.mjs b/tests/__internal__/replace_placerholders.test.mjs index 889608635f..81a3bb31af 100644 --- a/tests/__internal__/replace_placerholders.test.mjs +++ b/tests/__internal__/replace_placerholders.test.mjs @@ -1,14 +1,17 @@ import { assert } from "@jsenv/assert" -import { replacePlaceholders } from "@jsenv/core" +import { replacePlaceholders } from "@jsenv/core/src/plugins/placeholders/replace_placeholders.js" const result = replacePlaceholders( - `const foo = __FOO__ + { + type: "js_module", + content: `const foo = __FOO__ const t = __FOO__ const bar = __BAR__`, + }, { - __FOO__: JSON.stringify("hello"), - __BAR__: JSON.stringify("world"), + __FOO__: "hello", + __BAR__: "world", }, ) const actual = result.content diff --git a/tests/dev_and_build/inject_globals/client/main.html b/tests/dev_and_build/inject_globals/client/main.html new file mode 100644 index 0000000000..92821594a0 --- /dev/null +++ b/tests/dev_and_build/inject_globals/client/main.html @@ -0,0 +1,16 @@ + + +
+