requirejs-esm 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "requirejs-esm",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "A RequireJS plugin converting JavaScript modules from ESM to AMD.",
5
5
  "author": "Ferdinand Prantl <prantlf@gmail.com>",
6
6
  "license": "MIT",
@@ -21,13 +21,16 @@
21
21
  "engines": {
22
22
  "node": ">=14"
23
23
  },
24
- "main": "dist/plugin.js",
24
+ "main": "dist/api.js",
25
+ "module": "src/api.js",
26
+ "types": "src/api.d.ts",
25
27
  "bin": {
26
28
  "esm2requirejs": "bin/esm2requirejs.js"
27
29
  },
28
30
  "files": [
29
31
  "bin",
30
- "dist"
32
+ "dist",
33
+ "src"
31
34
  ],
32
35
  "scripts": {
33
36
  "prepare": "npm run build && npm run demo",
@@ -39,10 +42,11 @@
39
42
  "demo:local": "npm run optimize:local && npm run minify:local",
40
43
  "optimize:local": "r.js -o demo-local/build.config.js",
41
44
  "minify:local": "terser -cm --ecma 2018 --comments false --source-map content=demo-local/main-built.js.map --source-map includeSources --source-map url=main-built.min.js.map -o demo-local/main-built.min.js demo-local/main-built.js",
42
- "lint": "eslint -c .eslintrc.browser.yml src && eslint -c .eslintrc.node.yml *.js perf/*.js bin 'test/*.js'",
45
+ "lint": "eslint -c .eslintrc.browser.yml src && eslint -c .eslintrc.node.yml perf/*.js bin 'test/*.js'",
43
46
  "check": "tehanu test/*.js",
44
47
  "cover": "c8 node test && c8 --no-clean ./bin/esm2requirejs.js && c8 --no-clean ./bin/esm2requirejs.js test/input/esm-import-all.js && c8 --no-clean ./bin/esm2requirejs.js dummy || c8 report -r text -r lcov | grep -Ev '(\\.\\.\\.)|(index.mjs)|(source-map.js)|(base64-vlq.js)|(base64.js)|(binary-search.js)|(mapping-list.js)|(read-wasm-browser.js)|(url-browser.js)|(wasm.js)|(regexes.js)|(public-api.js)|(All files)' && c8 check-coverage",
45
- "test": "npm run lint && npm run cover"
48
+ "test": "npm run lint && npm run cover",
49
+ "start": "python3 -m http.server"
46
50
  },
47
51
  "tehanu": {
48
52
  "autostart": false
@@ -78,29 +82,28 @@
78
82
  "@semantic-release/git"
79
83
  ]
80
84
  },
81
- "devDependencies": {
85
+ "dependencies": {
82
86
  "@prantlf/convert-source-map": "^2.0.0",
83
- "@prantlf/requirejs": "^2.5.0",
87
+ "astring": "^1.8.1",
88
+ "charcodes": "^0.2.0",
89
+ "commander": "^9.1.0",
90
+ "meriyah": "^4.2.1",
91
+ "punycode": "^2.1.1",
92
+ "source-map": "^0.8.0-beta.0",
93
+ "tiny-glob": "^0.2.9"
94
+ },
95
+ "devDependencies": {
96
+ "@prantlf/requirejs": "^3.0.0",
84
97
  "@rollup/plugin-commonjs": "^21.0.3",
85
98
  "@rollup/plugin-json": "^4.1.0",
86
99
  "@rollup/plugin-node-resolve": "^13.1.3",
87
100
  "@semantic-release/changelog": "^6.0.1",
88
101
  "@semantic-release/git": "^10.0.1",
89
- "astring": "^1.8.1",
90
102
  "c8": "^7.11.0",
91
- "charcodes": "^0.2.0",
92
- "connect": "^3.7.0",
93
- "connect-block-favicon": "^1.0.4",
94
- "escodegen": "^2.0.0",
95
- "eslint": "^8.12.0",
103
+ "eslint": "^8.13.0",
96
104
  "lit-html": "^1",
97
- "meriyah": "^4.2.1",
98
- "morgan": "^1.10.0",
99
105
  "rollup": "^2.70.1",
100
- "serve-index": "^1.9.1",
101
- "serve-static": "^1.15.0",
102
- "source-map": "^0.8.0-beta.0",
103
- "tehanu": "^0.2.1",
106
+ "tehanu": "^0.2.2",
104
107
  "tehanu-repo-coco": "^0.0.2",
105
108
  "tehanu-teru": "^0.2.2",
106
109
  "terser": "^5.12.1"
@@ -112,12 +115,5 @@
112
115
  "amd",
113
116
  "esm",
114
117
  "es6"
115
- ],
116
- "dependencies": {
117
- "commander": "^9.1.0",
118
- "cors": "^2.8.5",
119
- "mime": "^3.0.0",
120
- "polka": "^0.5.2",
121
- "tiny-glob": "^0.2.9"
122
- }
118
+ ]
123
119
  }
package/src/api.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ type NeedsResolve = (sourcePath: string, currentFile: string) => boolean
2
+
3
+ interface ResolveOptions {
4
+ pluginName?: string
5
+ needsResolve?: NeedsResolve
6
+ }
7
+
8
+ declare function resolvePath(sourcePath: string, currentFile: string, options?: ResolveOptions): string
9
+
10
+ type ResolvePath = ((sourcePath: string, currentFile: string, options?: ResolveOptions) => string) | false
11
+
12
+ declare function transform(contents: string, path: string, options?: {
13
+ pluginName?: string /*= 'esm'' */, resolvePath?: ResolvePath,
14
+ sourceMap?: boolean /*= true */ }): string
package/src/api.js ADDED
@@ -0,0 +1,2 @@
1
+ export { resolvePath } from './resolve-path'
2
+ export { default as transform } from './transform'
@@ -0,0 +1,35 @@
1
+ /* global require */
2
+
3
+ let fetchText
4
+
5
+ // Initialise the fetchText variable with a function to download
6
+ // from a URL or to read from the file system.
7
+ /* istanbul ignore if */
8
+ if (typeof window !== 'undefined' && window.navigator && window.document) {
9
+ fetchText = (url, callback) => {
10
+ const xhr = new XMLHttpRequest()
11
+ xhr.open('GET', url, true)
12
+ xhr.onreadystatechange = () => {
13
+ if (xhr.readyState === 4) {
14
+ if (xhr.status === 200) {
15
+ callback(null, xhr.responseText)
16
+ } else {
17
+ callback(new Error(xhr.statusText))
18
+ }
19
+ }
20
+ }
21
+ xhr.send(null)
22
+ }
23
+ } else {
24
+ const { readFileSync } = require.nodeRequire ? require.nodeRequire('fs') : require('fs')
25
+ fetchText = (path, callback) => {
26
+ // Asynchronous reading is not possible during the build in the optimizer.
27
+ try {
28
+ callback(null, readFileSync(path, 'utf8'))
29
+ } catch (error) {
30
+ callback(error)
31
+ }
32
+ }
33
+ }
34
+
35
+ export default fetchText
package/src/plugin.js ADDED
@@ -0,0 +1,147 @@
1
+ /* global module */
2
+
3
+ import fetchText from './fetch-text'
4
+ import writeText from './write-text'
5
+ import { childFile } from './resolve-path'
6
+ import { setSkipModules, skipModule } from './skip-module'
7
+ import transformSource from './transform'
8
+
9
+ const {
10
+ // Allow using a different plugin alias than `esm` in the source code.
11
+ pluginName,
12
+ // Assume that the original sources are JavaScript files by default.
13
+ fileExtension = '.js',
14
+ // Flags to enforce or suppress the transpilation of not yet defined modules.
15
+ mixedAmdAndEsm,
16
+ onlyAmd,
17
+ // List of module names not to check and transform.
18
+ skipModules = [],
19
+ // Method to update paths of module dependencies, to prefix JavaScript module
20
+ // name with `esm!`, above all.
21
+ resolvePath,
22
+ // ecmaVersion,
23
+ // Boolean or object with booleans { inline, content }.
24
+ sourceMap,
25
+ // Enable console logging.
26
+ verbose,
27
+ // Directory to save a copy of the transformed modules.
28
+ debugDir
29
+ } = typeof module !== 'undefined' && module.config && module.config() || {}
30
+
31
+ const buildMap = {}
32
+
33
+ setSkipModules(skipModules)
34
+
35
+ //>>excludeEnd('excludeEsm')
36
+ export default {
37
+ load(name, req, onload, reqConfig) {
38
+ //>>excludeStart('excludeEsm', pragmas.excludeEsm)
39
+ const { isBuild } = reqConfig
40
+ // if (!bundledModules) {
41
+ // bundledModules = new Set()
42
+ // const { bundles } = reqConfig
43
+ // if (bundles) {
44
+ // verbose && console.log('esm: initialising bundles', name)
45
+ // for (const bundle in bundles) {
46
+ // for (const module of bundles[bundle]) {
47
+ // bundledModules.add(module)
48
+ // }
49
+ // }
50
+ // }
51
+ // }
52
+
53
+ // Paths relative to the current directory include the file extension.
54
+ // Otherwise the file extension must not be used for JavaScript modules.
55
+ // The file name should include the orioginal extension in the source maps.
56
+ const file = name.endsWith(fileExtension) ? name : name + fileExtension
57
+ // Compilation and bundling of module sub-trees can be skipped, if those
58
+ // are mapped to the pseudo-path `empty:`, meaning that those modules
59
+ // are external and will be loaded during the runtime.
60
+ const url = req.toUrl(file)
61
+ if (url.startsWith('empty:')) {
62
+ verbose && console.log('esm: skipping', name, 'mapped to', url)
63
+ return onload()
64
+ }
65
+
66
+ verbose && console.log('esm: loading', name)
67
+ // If the module has been already defined from a module bundle, it was
68
+ // already transpiled, when the output bundle was written. No need to
69
+ // re-transpile it. This can happen only during the runtime.
70
+ //
71
+ // The re-transpilation is however necessary, if a testing page or a unit
72
+ // test loads source ESM modules with extensions, which are going to be
73
+ // consumed by a bundled module. Then the bundled module has to prefix
74
+ // each of its dependencies with `esm!` to ensure their transpilation.
75
+ // This mixed mode can to be enabled by the flag `mixedAmdAndEsm`, if needed.
76
+ //
77
+ // If the whole application is transpiled, there is no need to transpile
78
+ // ESM modules or prefix dependencies of AMD modules. Even not yet defined
79
+ // modules can be loaded just by `require` to get better performance.
80
+ if (!mixedAmdAndEsm && !isBuild && req.specified(name) ||
81
+ /*bundledModules.has(name) ||*/ onlyAmd || skipModule(name) ||
82
+ // If the module was bundled, it had to be already transpiled.
83
+ !isBuild && childFile(url) !== childFile(file)) {
84
+ verbose && console.log('esm: delegating', name)
85
+ //>>excludeEnd('excludeEsm')
86
+ return req([name], onload, onload.error)
87
+ //>>excludeStart('excludeEsm', pragmas.excludeEsm)
88
+ }
89
+
90
+ // Fetch the text of the source module by AJAX and transpile it.
91
+ verbose && console.log('esm: fetching', name, 'mapped to', url)
92
+ fetchText(url, async (error, text) => {
93
+ if (error) {
94
+ verbose && console.log('esm: missing', name)
95
+ return onload.error(error)
96
+ }
97
+
98
+ let code, updated
99
+ try {
100
+ verbose && console.log('esm: transforming', name);
101
+ ({ code, updated } = transformSource(text, file, {
102
+ pluginName,
103
+ resolvePath,
104
+ /*ecmaVersion,*/
105
+ // Always produce the source maps when transpiling in the browser, otherwise
106
+ // the debugging would me impossible. When building and bundling, check if
107
+ // the source maps were enabled for the output.
108
+ sourceMap: sourceMap || !isBuild
109
+ }))
110
+ if (!updated) {
111
+ verbose && console.log('esm: retaining', name)
112
+ // return req([name], onload, onload.error)
113
+ } else if (isBuild && debugDir) {
114
+ writeText(`${debugDir}/${file}`, code)
115
+ }
116
+ } catch (error) {
117
+ // RequireJS did not always log this error.
118
+ console.error(`Transforming "${name}" (resolved to "${url}") failed:`)
119
+ console.error(error)
120
+ return onload.error(error)
121
+ }
122
+
123
+ // Remember the transpiled content for the writing phase during the build.
124
+ if (isBuild) {
125
+ buildMap[name] = code
126
+ }
127
+
128
+ verbose && console.log('esm: returning', name)
129
+ onload.fromText(code)
130
+ })
131
+ },
132
+
133
+ write(pluginName, moduleName, write) {
134
+ const code = buildMap[moduleName]
135
+ if (code) {
136
+ // Add the transpiled module under the original name. Earlier modules
137
+ // refer it with that name, before the module was converted to ESM.
138
+ write.asModule(moduleName, code)
139
+ // Add a stub of the module with the name prefixed by `esm!`. Modules
140
+ // compiled with esm refer it with that name and this stub will simplify
141
+ // the module loading by skipping the plugin evaluation.
142
+ write.asModule(`${pluginName}!${moduleName}`,
143
+ '\ndefine([\'' + moduleName + '\'], res => res);\n')
144
+ }
145
+ //>>excludeEnd('excludeEsm')
146
+ }
147
+ }
@@ -0,0 +1,80 @@
1
+ // Returns the child file name by cutting the directoris before the last slash,
2
+ // including the slash. If there is no slash in the path - the path is just
3
+ // a file name, it will return the file name. If the path includes a URL
4
+ // query starting with the question mark, it will cut it away including the
5
+ // question mark.
6
+ export function childFile (path) {
7
+ const lastSlash = path.lastIndexOf('/')
8
+ if (lastSlash > 0) {
9
+ path = path.substring(lastSlash + 1)
10
+ }
11
+ const questionMark = path.lastIndexOf('?')
12
+ return questionMark > 0 ? path.substring(0, questionMark) : path
13
+ }
14
+
15
+ // Returns the parent directory by cutting the file name after the last slash,
16
+ // leaving the slash in the result. If there is no slash in the path - the path
17
+ // is just a file name, it will return `undefined`.
18
+ function parentDir (path) {
19
+ const lastSlash = path.lastIndexOf('/')
20
+ return lastSlash > 0 ? path.substring(0, lastSlash + 1) : undefined
21
+ }
22
+
23
+ // Trims the leading "./" off the path.
24
+ function shortenPath(path) {
25
+ while (path.charAt(0) === '.' && path.charAt(1) === '/') {
26
+ path = path.substring(2)
27
+ }
28
+ return path
29
+ }
30
+
31
+ // Checks if the path starts with "../".
32
+ function pointsToParent(path) {
33
+ return path.charAt(0) === '.' && path.charAt(1) === '.' && path.charAt(2) === '/'
34
+ }
35
+
36
+ // Joins two paths together and avoids leaving "/./" or "/../" in the middle.
37
+ function joinPath (first, second) {
38
+ // If the second part starts with "./", it can be safely removed.
39
+ second = shortenPath(second)
40
+ // The parent path can be undefined, if the file is located in the current directory.
41
+ if (first !== undefined) {
42
+ // As long as "../" can be removed from the second path, shorten the first one.
43
+ while (pointsToParent(second)) {
44
+ // Remove the leading "../" and trim futher all leading "./".
45
+ second = shortenPath(second.substring(3))
46
+ // Cut the last directory from the first path.
47
+ first = parentDir(first)
48
+ // If the last part of the first path was removed and there is no parent
49
+ // directory to go further, return the rest of the second path.
50
+ if (first === undefined) {
51
+ return second
52
+ }
53
+ }
54
+ // Return what remains from the first path concatenated with the second one.
55
+ second = first + second
56
+ }
57
+ return second
58
+ }
59
+
60
+ // Ensures that every JavaScript dependency will be prefixed by `esm!`.
61
+ // Relative paths will be converted to be relative to the parent module.
62
+ export function resolvePath (sourcePath, currentFile, { pluginName, needsResolve } = {}) {
63
+ // Ignore paths with other plugins applied and the three built-in
64
+ // pseudo-modules of RequireJS.
65
+ if (sourcePath.includes('!') || sourcePath === 'require' ||
66
+ sourcePath === 'module' || sourcePath === 'exports') return
67
+
68
+ // If `sourcePath` is relative to `currentFile` - starts with ./ or ../ -
69
+ // prepend the parent directory of `currentFile` to it. This was needed
70
+ // for modules located outside the source root (`baseUrl`), which were
71
+ // mapped there using the `paths` of `map` configuration properties.
72
+ if ((sourcePath.charAt(0) === '.' && (sourcePath.charAt(1) === '/' ||
73
+ sourcePath.charAt(1) === '.' && sourcePath.charAt(2) === '/')) &&
74
+ !(needsResolve && needsResolve(sourcePath, currentFile))) {
75
+ sourcePath = joinPath(parentDir(currentFile), sourcePath)
76
+ if (sourcePath.endsWith('.js')) sourcePath = sourcePath.substring(0, sourcePath.length - 3)
77
+ }
78
+
79
+ return pluginName ? `${pluginName}!${sourcePath}` : sourcePath
80
+ }
@@ -0,0 +1,18 @@
1
+ let skipModules = []
2
+
3
+ export function setSkipModules(names) {
4
+ skipModules = names
5
+ }
6
+
7
+ // Checks if a module name is included in the list of modules to skip
8
+ // the transformation.
9
+ export function skipModule(name) {
10
+ for (const skipModule of skipModules) {
11
+ // Recognise an asterisk at the beginning
12
+ if (skipModule.startsWith('*')) {
13
+ if (name.indexOf(skipModule.substring(1)) >= 0) return true
14
+ }
15
+ // Accept a path prefix as well.
16
+ else if (name.startsWith(skipModule)) return true
17
+ }
18
+ }
@@ -0,0 +1,47 @@
1
+ import { resolvePath as originalResolvePath } from './resolve-path'
2
+ // import { parse } from 'acorn'
3
+ import { parseModule } from 'meriyah'
4
+ import { generate } from 'astring'
5
+ import { SourceMapGenerator } from 'source-map'
6
+ import convert from '@prantlf/convert-source-map'
7
+ import transformModules from './transformer'
8
+
9
+ export default function transform(text, file, {
10
+ // Allow using a different plugin alias than `esm` in the source code.
11
+ pluginName = 'esm',
12
+ // Method to update paths of module dependencies, to prefix JavaScript module
13
+ // name with `esm!`, above all.
14
+ resolvePath = originalResolvePath,
15
+ // ecmaVersion = 2020,
16
+ sourceMap
17
+ } = {}) {
18
+ // const ast = parse(text, { ecmaVersion, sourceType: 'module', locations: true })
19
+ let ast = parseModule(text, { next: true, loc: true })
20
+
21
+ const options = { sourceFileName: file, pluginName, resolvePath, originalResolvePath }
22
+ transformModules(ast, options)
23
+
24
+ const { updated } = options
25
+ let code, map
26
+ if (updated) {
27
+ if (sourceMap === true) {
28
+ sourceMap = { inline: true, content: true }
29
+ }
30
+ const mapGenerator = sourceMap ? new SourceMapGenerator({ file }) : undefined
31
+ code = generate(ast, { sourceMap: mapGenerator })
32
+ if (sourceMap) {
33
+ if (sourceMap.content) {
34
+ mapGenerator.setSourceContent(file, text)
35
+ }
36
+ if (sourceMap.inline) {
37
+ code += convert.fromObject(mapGenerator.toJSON()).toComment()
38
+ } else {
39
+ map = mapGenerator.toJSON()
40
+ }
41
+ }
42
+ } else {
43
+ code = text
44
+ }
45
+
46
+ return { code, map, updated }
47
+ }
@@ -0,0 +1,98 @@
1
+ import { replaceLiteral } from './converters'
2
+
3
+ // Detects if an expression calls define or require function.
4
+ // Returns information about an AMD module false, { deps } or [{ deps }, ...].
5
+ function detectDefineOrRequireCall(expr) {
6
+ if (expr.type !== 'CallExpression') return false
7
+
8
+ const args = expr.arguments
9
+ const { length } = args
10
+ if (length === 0) return false
11
+
12
+ const { callee } = expr
13
+ let func
14
+ // namespace.define(...)
15
+ if (callee.type === 'MemberExpression') {
16
+ const { object } = callee
17
+ if (object.type !== 'Identifier') return false
18
+ func = callee.property
19
+ } else {
20
+ func = callee
21
+ }
22
+ if (func.type !== 'Identifier') return false
23
+
24
+ // define('name', [deps], factory)
25
+ if (func.name === 'define') {
26
+ let index = 0
27
+ let arg = args[index]
28
+ let deps
29
+ if (arg.type === 'Literal') {
30
+ if (length <= ++index || typeof arg.value !== 'string') return false
31
+ arg = args[index]
32
+ }
33
+ if (arg.type === 'ArrayExpression') {
34
+ deps = arg
35
+ if (length <= ++index) return false
36
+ arg = args[index]
37
+ }
38
+ return (arg.type === 'FunctionExpression' || arg.type === 'ObjectExpression') &&
39
+ { deps: deps }
40
+ }
41
+
42
+ // require([deps], success, error)
43
+ if (func.name === 'require') {
44
+ const deps = args[0]
45
+ return deps.type === 'ArrayExpression' && length >= 2 &&
46
+ args[1].type === 'FunctionExpression' && { deps: deps }
47
+ }
48
+ }
49
+
50
+ // Detects if a program contains statements calling define or require function.
51
+ // Returns information about AMD modules false, { deps } or [{ deps }, ...].
52
+ function detectDefineOrRequire(stat) {
53
+ if (stat.type !== 'ExpressionStatement') return false
54
+ const { expression } = stat
55
+
56
+ // multiple define/require statements in one file
57
+ if (expression.type === 'SequenceExpression') {
58
+ return expression
59
+ .expressions
60
+ .map(detectDefineOrRequireCall)
61
+ .reduce((all, one) => one ? all.concat(one) : all, [])
62
+ }
63
+
64
+ return detectDefineOrRequireCall(expression)
65
+ }
66
+
67
+ // Detects if a program contains statements calling define or require function.
68
+ // Returns information about the AMD modules [{ deps }, ...] or [].
69
+ export function detectDefinesOrRequires(program) {
70
+ const { body } = program
71
+ return body
72
+ .map(detectDefineOrRequire)
73
+ .reduce((all, one) => one ? all.concat(one) : all, [])
74
+ }
75
+
76
+ // Updates dependency paths to be prefixed by `esm!` or otherwise updated.
77
+ export function updateAmdDeps(amd, options) {
78
+ const { deps } = amd
79
+ if (!deps) return
80
+
81
+ const { sourceFileName: parentName } = options
82
+ const { elements } = deps
83
+ if (!elements.length) return
84
+
85
+ const { resolvePath } = options
86
+ let updated
87
+ for (const element of elements) {
88
+ if (element.type === 'Literal') {
89
+ const moduleName = element.value
90
+ const newModuleName = resolvePath(moduleName, parentName, options)
91
+ if (newModuleName && newModuleName !== moduleName) {
92
+ replaceLiteral(element, newModuleName)
93
+ updated = true
94
+ }
95
+ }
96
+ }
97
+ return updated
98
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ identifier, memberExpression, assignmentExpression, expressionStatement
3
+ } from './factories'
4
+ import { isValidIdentifier } from './validators'
5
+ import { isIdentifierChar } from './identifier'
6
+
7
+ export function toIdentifier(input) {
8
+ input = input + ''
9
+
10
+ let name = ''
11
+ for (const c of input) {
12
+ name += isIdentifierChar(c.codePointAt(0)) ? c : '-'
13
+ }
14
+
15
+ name = name.replace(/^[-0-9]+/, '')
16
+
17
+ name = name.replace(/[-\s]+(.)?/g, function (match, c) {
18
+ return c ? c.toUpperCase() : ''
19
+ })
20
+
21
+ if (!isValidIdentifier(name)) {
22
+ name = `_${name}`
23
+ }
24
+
25
+ return name || '_'
26
+ }
27
+
28
+ export function toExpression(node) {
29
+ if (node.type === 'ExpressionStatement') {
30
+ node = node.expression
31
+ }
32
+
33
+ const { type } = node
34
+
35
+ if (type.endsWith('Expression')) return node
36
+
37
+ if (type === 'ClassDeclaration') {
38
+ node.type = 'ClassExpression'
39
+ } else if (type === 'FunctionDeclaration') {
40
+ node.type = 'FunctionExpression'
41
+ }
42
+
43
+ if (!node.type.endsWith('Expression')) {
44
+ throw new Error(`cannot turn ${node.type} to an expression`)
45
+ }
46
+
47
+ return node
48
+ }
49
+
50
+ export function toStatement(node, ignore) {
51
+ const { type } = node
52
+
53
+ if (type.endsWith('Statement')) return node
54
+
55
+ let mustHaveId = false
56
+ let newType
57
+
58
+ if (type === 'ClassDeclaration' || type === 'ClassExpression') {
59
+ mustHaveId = true
60
+ newType = 'ClassDeclaration'
61
+ } else if (type === 'FunctionDeclaration' || type === 'FunctionExpression' ||
62
+ type === 'ArrowFunctionExpression') {
63
+ mustHaveId = true
64
+ newType = 'FunctionDeclaration'
65
+ } else if (type === 'AssignmentExpression') {
66
+ return expressionStatement(node)
67
+ }
68
+
69
+ if (mustHaveId && !node.id) {
70
+ newType = false
71
+ }
72
+
73
+ if (!newType) {
74
+ if (ignore) {
75
+ return false
76
+ } else {
77
+ throw new Error(`cannot turn ${node.type} to a statement`)
78
+ }
79
+ }
80
+
81
+ node.type = newType
82
+
83
+ return node
84
+ }
85
+
86
+ export function replaceLiteral(literal, value) {
87
+ literal.value = value
88
+ const { raw } = literal
89
+ if (raw === undefined) return
90
+ if (typeof value === 'string') {
91
+ literal.raw = String(raw).charAt(0) === "'" ?
92
+ `'${value.replaceAll("'", "\\'")}'` : `"${value.replaceAll('"', '\\"')}"`
93
+ } else {
94
+ literal.raw = String(value)
95
+ }
96
+ }
97
+
98
+ // Returns a statement assigning a property value to the exports object.
99
+ export function exportStatement(exportsVar, key, value) {
100
+ return toStatement(assignmentExpression('=',
101
+ memberExpression(exportsVar, identifier(key)), value))
102
+ }