From 483f769fad3a0f2da8ecc53da32f0b32fa74216a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=83=E5=B9=B4?= <2665238047@qq.com> Date: Sun, 17 Nov 2024 06:15:29 +0800 Subject: [PATCH] Add CDN import module function (only supports ESM modules). --- README.md | 77 ++++++++++++++++++++++++++++++++++++++-- index.d.ts | 15 ++++++-- index.js | 10 ++++++ lib/import-to-globals.js | 53 +++++++++++++++++++++------ package.json | 2 +- 5 files changed, 142 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d5f8ce6..8ca76e1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Installation npm install -D rollup-plugin-external-globals ``` -Usage +Usage 1 ----- ```js @@ -69,6 +69,65 @@ Promise.resolve($) > **Note:** this plugin only works with import/export syntax. If you are using a module loader transformer e.g. `rollup-plugin-commonjs`, you have to put this plugin *after* the transformer plugin. +Usage 2 +----- +```js +import externalGlobals from "rollup-plugin-external-globals"; + +export default { + input: ["entry.js"], + output: { + dir: "dist", + format: "es" + }, + plugins: [ + externalGlobals({ + jquery: { + name: "$", + esmUrl: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/+esm" + } + }) + ] +}; +``` + +The above config transforms + +```js +import jq from "jquery"; + +console.log(jq(".test")); +``` + +into + +```js +import $ from "https://cdn.jsdelivr.net/npm/jquery@3.7.1/+esm"; + +console.log($(".test")); +``` + +It also transforms dynamic import: + +```js +import("jquery") + .then($ => { + $ = $.default || $; + console.log($(".test")); + }); + +// transformed +import("https://cdn.jsdelivr.net/npm/jquery@3.7.1/+esm") + .then($ => { + $ = $.default || $; + console.log($(".test")); + }); +``` + +> **Note:** when using dynamic import, you should notice that in ES module, the resolved object is aways a module namespace, but the global variable might be not. + +> **Note:** this plugin only works with import/export syntax. If you are using a module loader transformer e.g. `rollup-plugin-commonjs`, you have to put this plugin *after* the transformer plugin. + API ---- @@ -78,7 +137,7 @@ This module exports a single function. ```js const plugin = createPlugin( - globals: Object | Function, + globals: Object | Function | {name: String, esmUrl?: String}, { include?: Array, exclude?: Array, @@ -96,6 +155,17 @@ const globals = { } ``` +or `globals` is a `moduleId`/`variableName and (ESM)CDN URL` map,`(ESM)CDN URL` is optional. For example, to map `jquery` module to `{name: "$", esmUrl: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/+esm"}`: + +```js +const globals = { + jquery: { + name: "$", + esmUrl: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/+esm" + } +} +``` + or provide a function that takes the `moduleId` and returns the `variableName`. ```js @@ -124,6 +194,9 @@ Virtual modules are always transformed. Changelog --------- +* 0.12.2 (Nov 17, 2024) + + - Add: CDN import module function (only supports ESM modules). * 0.12.1 (Nov 15, 2024) diff --git a/index.d.ts b/index.d.ts index 5b852f9..4ec57ec 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,12 +1,23 @@ import type { Plugin } from "rollup"; export type VariableName = string; + +export type ModuleConfig = { + /** + * [name] is the global name of the module + */ + name:string; + /** + * [esmUrl] is the CDN URL for the ESM version of the module + */ + esmUrl?:string; +}; /** - * globals is a moduleId/variableName map + * globals is a moduleId/variableName or moduleId/variableName and (ESM)CDN URL map * or provide a function that takes the moduleId and returns the variableName */ export type ModuleNameMap = - | Record + | Record | ((id: string) => VariableName); export type ExternalGlobalsOptions = { diff --git a/index.js b/index.js index 275ebef..b6218df 100644 --- a/index.js +++ b/index.js @@ -18,8 +18,18 @@ function createPlugin(globals, {include, exclude, dynamicWrapper = defaultDynami if (isGlobalsObj) { getName = function (name) { if (Object.prototype.hasOwnProperty.call(globals, name)) { + if(!globals[name]){ + throw new TypeError("Missing mandatory option 'globals'"); + } + else if(globals[name] instanceof Object&&Object.keys(globals[name]).length === 0){ + throw new TypeError("Missing mandatory option 'globals'"); + } return globals[name]; } + else if(Object.keys(globals).length === 0){ + throw new TypeError("Missing mandatory option 'globals'"); + } + }; } else if (globalsType !== "function") { throw new TypeError(`Unexpected type of 'globals', got '${globalsType}'`); diff --git a/lib/import-to-globals.js b/lib/import-to-globals.js index 7fcf435..a4c5fb1 100644 --- a/lib/import-to-globals.js +++ b/lib/import-to-globals.js @@ -14,14 +14,26 @@ function analyzeImport(node, importBindings, code, getName, globals) { if (!name) { return false; } - globals.add(name); + let globalName; + if(typeof name === "string"){ + globalName=name; + } + else if((name instanceof Object)&&Object.prototype.hasOwnProperty.call(name, "name")){ + globalName=name.name; + } + globals.add(globalName); for (const spec of node.specifiers) { importBindings.set(spec.local.name, makeGlobalName( spec.imported ? spec.imported.name : "default", - name + globalName )); } - code.remove(node.start, node.end); + if(typeof name === "string"||!Object.prototype.hasOwnProperty.call(name, 'esmUrl')){ + code.remove(node.start, node.end); + } + else if(Object.prototype.hasOwnProperty.call(name, 'esmUrl')){ + code.overwrite(node.start,node.end, `import ${globalName} from "${name.esmUrl}"`, { contentOnly: true }); + } return true; } @@ -32,13 +44,16 @@ function makeGlobalName(prop, name) { return `${name}.${prop}`; } -function writeSpecLocal(code, root, spec, name, tempNames, constBindings) { +function writeSpecLocal(code, root, spec, name, tempNames, constBindings,globalValue) { if (spec.isOverwritten) return; // we always need an extra assignment for named export statement // https://github.com/eight04/rollup-plugin-external-globals/issues/19 const localName = `_global_${makeLegalIdentifier(name)}`; if (!tempNames.has(localName)) { code.appendRight(root.start, `${constBindings ? "const" : "var"} ${localName} = ${name};\n`); + if(globalValue instanceof Object&&Object.prototype.hasOwnProperty.call(globalValue, "esmUrl")){ + code.appendRight(0, `import ${globalValue.name} from "${globalValue.esmUrl}";`) + } tempNames.add(localName); } if (spec.local.name === localName) { @@ -81,8 +96,14 @@ function analyzeExportNamed(node, code, getName, tempNames, constBindings) { return false; } for (const spec of node.specifiers) { - const globalName = makeGlobalName(spec.local.name, name); - writeSpecLocal(code, node, spec, globalName, tempNames, constBindings); + let globalName; + if(typeof name === "string"){ + globalName=makeGlobalName(spec.local.name, name); + } + else if((name instanceof Object)&&Object.prototype.hasOwnProperty.call(name, "name")){ + globalName=makeGlobalName(spec.local.name, name.name); + } + writeSpecLocal(code, node, spec, globalName, tempNames, constBindings,name); } if (node.specifiers.length) { code.overwrite(node.specifiers[node.specifiers.length - 1].end, node.source.end, "}"); @@ -167,10 +188,12 @@ async function importToGlobals({ast, code, getName, getDynamicWrapper, constBind if (node.scope) { scope = node.scope; } + const source = getDynamicImportSource(node); + let name= source && getName(source); if (isReference(node, parent)) { if (bindings.has(node.name) && !scope.contains(node.name)) { if (parent.type === "ExportSpecifier") { - writeSpecLocal(code, topStatement, parent, bindings.get(node.name), tempNames, constBindings); + writeSpecLocal(code, topStatement, parent, bindings.get(node.name), tempNames, constBindings,name); } else { writeIdentifier(code, node, parent, bindings.get(node.name)); } @@ -183,9 +206,19 @@ async function importToGlobals({ast, code, getName, getDynamicWrapper, constBind } } } - const source = getDynamicImportSource(node); - const name = source && getName(source); - const dynamicName = name && getDynamicWrapper(name); + let dynamicName; + if(source &&(getName(source) instanceof Object)&&Object.prototype.hasOwnProperty.call(getName(source), "name")){ + name = source && getName(source).name; + } + if(source &&(getName(source) instanceof Object)&&Object.prototype.hasOwnProperty.call(getName(source), "esmUrl")){ + dynamicName = name && getDynamicWrapper(`"${getName(source).esmUrl}"`); + if(typeof dynamicName==='string'&&/^Promise.resolve/i.test(dynamicName.replace(/\s+/g,''))){ + dynamicName =`import("${getName(source).esmUrl}")`; + } + } + else{ + dynamicName = name && getDynamicWrapper(name); + } if (dynamicName) { writeDynamicImport(code, node, dynamicName); isTouched = true; diff --git a/package.json b/package.json index 337ac0a..085be1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup-plugin-external-globals", - "version": "0.12.1", + "version": "0.12.2", "description": "Transform external imports into global variables like output.globals.", "keywords": [ "rollup-plugin",