|
| 1 | +// Based on https://github.com/handsontable/handsontable/blob/bd7628544ff83d6e74a9cc949e2c3c38fef74d76/handsontable/.config/plugin/babel/add-import-extension.js |
| 2 | + |
| 3 | +const { existsSync, lstatSync } = require('fs'); |
| 4 | +const { dirname, resolve } = require('path'); |
| 5 | +const { types } = require('@babel/core'); |
| 6 | +const { declare } = require('@babel/helper-plugin-utils'); |
| 7 | + |
| 8 | +const VALID_EXTENSIONS = ['js', 'mjs']; |
| 9 | + |
| 10 | +const hasExtension = moduleName => VALID_EXTENSIONS.some(ext => moduleName.endsWith(`.${ext}`)); |
| 11 | +const isCoreJSPolyfill = moduleName => moduleName.startsWith('core-js'); |
| 12 | +const isLocalModule = moduleName => moduleName.startsWith('.'); |
| 13 | +const isProcessableModule = (moduleName) => { |
| 14 | + return !hasExtension(moduleName) && (isCoreJSPolyfill(moduleName) || isLocalModule(moduleName)); |
| 15 | +}; |
| 16 | + |
| 17 | +const createVisitor = ({ declaration, origArgs, extension = 'js' }) => { |
| 18 | + return (path, { file }) => { |
| 19 | + const { node: { source, exportKind, importKind } } = path; |
| 20 | + const { opts: { filename } } = file; |
| 21 | + const isTypeOnly = exportKind === 'type' || importKind === 'type'; |
| 22 | + |
| 23 | + if (!source || isTypeOnly || !isProcessableModule(source.value)) { |
| 24 | + return; |
| 25 | + } |
| 26 | + |
| 27 | + const { value: moduleName } = source; |
| 28 | + const absoluteFilePath = resolve(dirname(filename), moduleName); |
| 29 | + const finalExtension = isCoreJSPolyfill(moduleName) ? 'js' : extension; |
| 30 | + |
| 31 | + let newModulePath; |
| 32 | + |
| 33 | + // Resolves a case where "import" points to a module name which exists as a file and |
| 34 | + // as a directory. For example in this case: |
| 35 | + // ``` |
| 36 | + // import { registerPlugin } from 'plugins'; |
| 37 | + // ``` |
| 38 | + // and with this directory structure: |
| 39 | + // |- editors |
| 40 | + // |- plugins |
| 41 | + // |- filters/ |
| 42 | + // |- ... |
| 43 | + // +- index.js |
| 44 | + // |- plugins.js |
| 45 | + // |- ... |
| 46 | + // +- index.js |
| 47 | + // |
| 48 | + // the plugin will rename import declaration to point to the `plugins.js` file. |
| 49 | + if (existsSync(`${absoluteFilePath}.js`)) { |
| 50 | + newModulePath = `${moduleName}.${finalExtension}`; |
| 51 | + |
| 52 | + // In a case when the file doesn't exist and the module is a directory it will |
| 53 | + // rename to `plugins/index.js`. |
| 54 | + } else if (existsSync(absoluteFilePath) && lstatSync(absoluteFilePath).isDirectory()) { |
| 55 | + newModulePath = `${moduleName}/index.${finalExtension}`; |
| 56 | + |
| 57 | + // And for other cases it simply put the extension on the end of the module path |
| 58 | + } else { |
| 59 | + newModulePath = `${moduleName}.${finalExtension}`; |
| 60 | + } |
| 61 | + |
| 62 | + path.replaceWith(declaration(...origArgs(path), types.stringLiteral(newModulePath))); |
| 63 | + }; |
| 64 | +}; |
| 65 | + |
| 66 | +module.exports = declare((api, options) => { |
| 67 | + api.assertVersion(7); |
| 68 | + |
| 69 | + return { |
| 70 | + name: 'add-import-extension', |
| 71 | + visitor: { |
| 72 | + // It covers default and named imports |
| 73 | + ImportDeclaration: createVisitor({ |
| 74 | + extension: options.extension, |
| 75 | + declaration: types.importDeclaration, |
| 76 | + origArgs: ({ node: { specifiers } }) => [specifiers], |
| 77 | + }), |
| 78 | + ExportNamedDeclaration: createVisitor({ |
| 79 | + extension: options.extension, |
| 80 | + declaration: types.exportNamedDeclaration, |
| 81 | + origArgs: ({ node: { declaration, specifiers } }) => [declaration, specifiers], |
| 82 | + }), |
| 83 | + ExportAllDeclaration: createVisitor({ |
| 84 | + extension: options.extension, |
| 85 | + declaration: types.exportAllDeclaration, |
| 86 | + origArgs: () => [], |
| 87 | + }), |
| 88 | + } |
| 89 | + }; |
| 90 | +}); |
0 commit comments