| 'use strict'; |
| |
| Object.defineProperty(exports, '__esModule', { value: true }); |
| |
| var path = require('path'); |
| var estreeWalker = require('estree-walker'); |
| var MagicString = require('magic-string'); |
| var fastGlob = require('fast-glob'); |
| var astring = require('astring'); |
| var pluginutils = require('@rollup/pluginutils'); |
| |
| class VariableDynamicImportError extends Error {} |
| |
| /* eslint-disable-next-line no-template-curly-in-string */ |
| const example = 'For example: import(`./foo/${bar}.js`).'; |
| |
| function sanitizeString(str) { |
| if (str === '') return str; |
| if (str.includes('*')) { |
| throw new VariableDynamicImportError('A dynamic import cannot contain * characters.'); |
| } |
| return fastGlob.escapePath(str); |
| } |
| |
| function templateLiteralToGlob(node) { |
| let glob = ''; |
| |
| for (let i = 0; i < node.quasis.length; i += 1) { |
| glob += sanitizeString(node.quasis[i].value.raw); |
| if (node.expressions[i]) { |
| glob += expressionToGlob(node.expressions[i]); |
| } |
| } |
| |
| return glob; |
| } |
| |
| function callExpressionToGlob(node) { |
| const { callee } = node; |
| if ( |
| callee.type === 'MemberExpression' && |
| callee.property.type === 'Identifier' && |
| callee.property.name === 'concat' |
| ) { |
| return `${expressionToGlob(callee.object)}${node.arguments.map(expressionToGlob).join('')}`; |
| } |
| return '*'; |
| } |
| |
| function binaryExpressionToGlob(node) { |
| if (node.operator !== '+') { |
| throw new VariableDynamicImportError(`${node.operator} operator is not supported.`); |
| } |
| |
| return `${expressionToGlob(node.left)}${expressionToGlob(node.right)}`; |
| } |
| |
| function expressionToGlob(node) { |
| switch (node.type) { |
| case 'TemplateLiteral': |
| return templateLiteralToGlob(node); |
| case 'CallExpression': |
| return callExpressionToGlob(node); |
| case 'BinaryExpression': |
| return binaryExpressionToGlob(node); |
| case 'Literal': { |
| return sanitizeString(node.value); |
| } |
| default: |
| return '*'; |
| } |
| } |
| |
| const defaultProtocol = 'file:'; |
| const ignoredProtocols = ['data:', 'http:', 'https:']; |
| |
| function shouldIgnore(glob) { |
| const containsAsterisk = glob.includes('*'); |
| |
| const globURL = new URL(glob, defaultProtocol); |
| |
| const containsIgnoredProtocol = ignoredProtocols.some( |
| (ignoredProtocol) => ignoredProtocol === globURL.protocol |
| ); |
| |
| return !containsAsterisk || containsIgnoredProtocol; |
| } |
| |
| function dynamicImportToGlob(node, sourceString) { |
| let glob = expressionToGlob(node); |
| |
| if (shouldIgnore(glob)) { |
| return null; |
| } |
| |
| glob = glob.replace(/\*\*/g, '*'); |
| |
| if (glob.startsWith('*')) { |
| throw new VariableDynamicImportError( |
| `invalid import "${sourceString}". It cannot be statically analyzed. Variable dynamic imports must start with ./ and be limited to a specific directory. ${example}` |
| ); |
| } |
| |
| if (glob.startsWith('/')) { |
| throw new VariableDynamicImportError( |
| `invalid import "${sourceString}". Variable absolute imports are not supported, imports must start with ./ in the static part of the import. ${example}` |
| ); |
| } |
| |
| if (!glob.startsWith('./') && !glob.startsWith('../')) { |
| throw new VariableDynamicImportError( |
| `invalid import "${sourceString}". Variable bare imports are not supported, imports must start with ./ in the static part of the import. ${example}` |
| ); |
| } |
| |
| // Disallow ./*.ext |
| const ownDirectoryStarExtension = /^\.\/\*\.\w+$/; |
| if (ownDirectoryStarExtension.test(glob)) { |
| throw new VariableDynamicImportError( |
| `${ |
| `invalid import "${sourceString}". Variable imports cannot import their own directory, ` + |
| 'place imports in a separate directory or make the import filename more specific. ' |
| }${example}` |
| ); |
| } |
| |
| if (path.extname(glob) === '') { |
| throw new VariableDynamicImportError( |
| `invalid import "${sourceString}". A file extension must be included in the static part of the import. ${example}` |
| ); |
| } |
| |
| return glob; |
| } |
| |
| function dynamicImportVariables({ include, exclude, warnOnError, errorWhenNoFilesFound } = {}) { |
| const filter = pluginutils.createFilter(include, exclude); |
| |
| return { |
| name: 'rollup-plugin-dynamic-import-variables', |
| |
| transform(code, id) { |
| if (!filter(id)) { |
| return null; |
| } |
| |
| const parsed = this.parse(code); |
| |
| let dynamicImportIndex = -1; |
| let ms; |
| |
| estreeWalker.walk(parsed, { |
| enter: (node) => { |
| if (node.type !== 'ImportExpression') { |
| return; |
| } |
| dynamicImportIndex += 1; |
| |
| let importArg; |
| if (node.arguments && node.arguments.length > 0) { |
| // stringify the argument node, without indents, removing newlines and using single quote strings |
| importArg = astring.generate(node.arguments[0], { indent: '' }) |
| .replace(/\n/g, '') |
| .replace(/"/g, "'"); |
| } |
| |
| try { |
| // see if this is a variable dynamic import, and generate a glob expression |
| const glob = dynamicImportToGlob(node.source, code.substring(node.start, node.end)); |
| |
| if (!glob) { |
| // this was not a variable dynamic import |
| return; |
| } |
| |
| // execute the glob |
| const result = fastGlob.sync(glob, { cwd: path.dirname(id) }); |
| const paths = result.map((r) => |
| r.startsWith('./') || r.startsWith('../') ? r : `./${r}` |
| ); |
| |
| if (errorWhenNoFilesFound && paths.length === 0) { |
| const error = new Error( |
| `No files found in ${glob} when trying to dynamically load concatted string from ${id}` |
| ); |
| if (warnOnError) { |
| this.warn(error); |
| } else { |
| this.error(error); |
| } |
| } |
| |
| // create magic string if it wasn't created already |
| ms = ms || new MagicString(code); |
| // unpack variable dynamic import into a function with import statements per file, rollup |
| // will turn these into chunks automatically |
| ms.prepend( |
| `function __variableDynamicImportRuntime${dynamicImportIndex}__(path) { |
| switch (path) { |
| ${paths |
| .map((p) => ` case '${p}': return import('${p}'${importArg ? `, ${importArg}` : ''});`) |
| .join('\n')} |
| ${` default: return new Promise(function(resolve, reject) { |
| (typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)( |
| reject.bind(null, new Error("Unknown variable dynamic import: " + path)) |
| ); |
| })\n`} } |
| }\n\n` |
| ); |
| |
| // call the runtime function instead of doing a dynamic import, the import specifier will |
| // be evaluated at runtime and the correct import will be returned by the injected function |
| ms.overwrite( |
| node.start, |
| node.start + 6, |
| `__variableDynamicImportRuntime${dynamicImportIndex}__` |
| ); |
| } catch (error) { |
| if (error instanceof VariableDynamicImportError) { |
| // TODO: line number |
| if (warnOnError) { |
| this.warn(error); |
| } else { |
| this.error(error); |
| } |
| } else { |
| this.error(error); |
| } |
| } |
| } |
| }); |
| |
| if (ms && dynamicImportIndex !== -1) { |
| return { |
| code: ms.toString(), |
| map: ms.generateMap({ |
| file: id, |
| includeContent: true, |
| hires: true |
| }) |
| }; |
| } |
| return null; |
| } |
| }; |
| } |
| |
| exports.VariableDynamicImportError = VariableDynamicImportError; |
| exports.default = dynamicImportVariables; |
| exports.dynamicImportToGlob = dynamicImportToGlob; |
| module.exports = Object.assign(exports.default, exports); |
| //# sourceMappingURL=index.js.map |