Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
feat(build): expose /es as a real es module (#4971)
* write package.json in /es submodule * add extensions * jest setup * update storybook & configure to deal with .js paths * fix a bunch of remaining imports * update rollup * ci(codesandbox): use v12 * storybook: try different babel config * different config again? * loose? * storybook alone * use babel plugin * rewrite extension rewriter * remove all extension changes * forbid extensions now * undo jest change * undo rollup changes * undo storybook changes * remove * remove * ts * thing * chore: update preact * ci: add ESM check * clean lockfile * fix lint * update bundlesize due to preact change * update node version a little, for esm support * update preact to latest * downgrade preact requirement
- Loading branch information
Showing
20 changed files
with
334 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"sandboxes": ["instantsearchjs-es-template-pcw1k"], | ||
"buildCommand": "build" | ||
"buildCommand": "build", | ||
"node": "12" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* eslint-disable import/no-commonjs */ | ||
|
||
const fs = jest.requireActual('fs'); | ||
|
||
let mockFiles = new Set(); | ||
function __setMockFiles(newMockFiles) { | ||
mockFiles = new Set(newMockFiles); | ||
} | ||
|
||
const realStatSync = fs.statSync; | ||
function statSync(pathName, ...args) { | ||
try { | ||
return realStatSync(pathName, ...args); | ||
} catch (e) { | ||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { | ||
if (mockFiles.has(pathName)) { | ||
return { | ||
isFile() { | ||
return true; | ||
}, | ||
}; | ||
} | ||
} | ||
throw e; | ||
} | ||
} | ||
|
||
fs.__setMockFiles = __setMockFiles; | ||
fs.statSync = statSync; | ||
|
||
module.exports = fs; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { transformAsync } from '@babel/core'; | ||
|
||
import plugin from '../extension-resolver'; | ||
import fs from 'fs'; | ||
jest.mock('fs'); | ||
|
||
describe('babel-plugin-extension-resolver', () => { | ||
const options = { | ||
filename: '/path/to/src/file.js', | ||
configFile: false, | ||
babelrc: false, | ||
plugins: [plugin], | ||
}; | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('name=babel-plugin-extension-resolver', () => | ||
expect(plugin({ types: {} }, {}).name).toStrictEqual( | ||
'babel-plugin-extension-resolver' | ||
)); | ||
|
||
it('ignores empty code', async () => { | ||
expect(await transformAsync('', options)).toHaveProperty('code', ''); | ||
}); | ||
|
||
it('ignores module imports', async () => { | ||
expect( | ||
await transformAsync('import path from "path";', options) | ||
).toHaveProperty('code', 'import path from "path";'); | ||
}); | ||
|
||
it('finds .js files', async () => { | ||
fs.__setMockFiles([ | ||
'/path/to/src/other.js', | ||
'/path/to/src/other.ts', | ||
'/path/to/src/other.tsx', | ||
]); | ||
|
||
expect( | ||
await transformAsync('import other from "./other";', options) | ||
).toHaveProperty('code', 'import other from "./other.js";'); | ||
}); | ||
|
||
it('finds .ts files', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.ts', '/path/to/src/other.tsx']); | ||
|
||
expect( | ||
await transformAsync('import other from "./other";', options) | ||
).toHaveProperty('code', 'import other from "./other.js";'); | ||
}); | ||
|
||
it('finds .tsx files', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.tsx']); | ||
|
||
expect( | ||
await transformAsync('import other from "./other";', options) | ||
).toHaveProperty('code', 'import other from "./other.js";'); | ||
}); | ||
|
||
it('finds files in parent directory', async () => { | ||
fs.__setMockFiles(['/path/to/other.js', '/path/to/src/other.js']); | ||
|
||
expect( | ||
await transformAsync('import other from "../other";', options) | ||
).toHaveProperty('code', 'import other from "../other.js";'); | ||
}); | ||
|
||
it('finds files in child directory', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.js', '/path/to/src/child/other.js']); | ||
|
||
expect( | ||
await transformAsync('import other from "./child/other";', options) | ||
).toHaveProperty('code', 'import other from "./child/other.js";'); | ||
}); | ||
|
||
it('uses index file', async () => { | ||
fs.__setMockFiles(['/path/to/src/other/index.js']); | ||
|
||
expect( | ||
await transformAsync('import other from "./other";', options) | ||
).toHaveProperty('code', 'import other from "./other/index.js";'); | ||
}); | ||
|
||
it('works with multiple imports', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.js', '/path/to/src/another.js']); | ||
|
||
expect( | ||
await transformAsync( | ||
'import other from "./other";\nimport another from "./another";', | ||
options | ||
) | ||
).toHaveProperty( | ||
'code', | ||
'import other from "./other.js";\nimport another from "./another.js";' | ||
); | ||
}); | ||
|
||
it('works with export from', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.js']); | ||
|
||
expect( | ||
await transformAsync('export * from "./other"', options) | ||
).toHaveProperty('code', 'export * from "./other.js";'); | ||
}); | ||
|
||
it('ignores require()', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.js', '/path/to/src/another.js']); | ||
|
||
expect(await transformAsync('require("./other");', options)).toHaveProperty( | ||
'code', | ||
'require("./other");' | ||
); | ||
}); | ||
|
||
it('ignores other function calls', async () => { | ||
fs.__setMockFiles(['/path/to/src/other.js']); | ||
|
||
expect( | ||
await transformAsync('requireOOPS("./other");', options) | ||
).toHaveProperty('code', 'requireOOPS("./other");'); | ||
}); | ||
|
||
it('leaves as-is if file not found', async () => { | ||
fs.__setMockFiles([]); | ||
|
||
await expect(() => | ||
transformAsync('import other from "./other";', options) | ||
).rejects.toThrow( | ||
'/path/to/src/file.js: local import for "./other" could not be resolved' | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* eslint-disable import/no-commonjs */ | ||
// original source: https://github.com/shimataro/babel-plugin-module-extension-resolver | ||
// To create proper ES Modules, the paths imported need to be fully-specified, | ||
// and can't be resolved like is possible with CommonJS. To have large compatibility | ||
// without much hassle, we don't use extensions *inside* the source code, but add | ||
// them with this plugin. | ||
|
||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const PLUGIN_NAME = 'babel-plugin-extension-resolver'; | ||
|
||
const srcExtensions = ['.js', '.ts', '.tsx']; | ||
const dstExtension = '.js'; | ||
|
||
module.exports = function extensionResolver(babel) { | ||
const { types } = babel; | ||
return { | ||
name: PLUGIN_NAME, | ||
visitor: { | ||
Program: { | ||
enter: (programPath, state) => { | ||
const { filename } = state; | ||
programPath.traverse( | ||
{ | ||
ImportDeclaration(declaration) { | ||
handleImportDeclaration(types, declaration, filename); | ||
}, | ||
ExportDeclaration(declaration) { | ||
handleExportDeclaration(types, declaration, filename); | ||
}, | ||
}, | ||
state | ||
); | ||
}, | ||
}, | ||
}, | ||
}; | ||
}; | ||
|
||
function handleImportDeclaration(types, declaration, fileName) { | ||
const source = declaration.get('source'); | ||
replaceSource(types, source, fileName); | ||
} | ||
|
||
function handleExportDeclaration(types, declaration, fileName) { | ||
const source = declaration.get('source'); | ||
if (Array.isArray(source)) { | ||
return; | ||
} | ||
replaceSource(types, source, fileName); | ||
} | ||
|
||
function replaceSource(types, source, fileName) { | ||
if (!source.isStringLiteral()) { | ||
return; | ||
} | ||
const sourcePath = source.node.value; | ||
if (sourcePath[0] !== '.') { | ||
return; | ||
} | ||
const baseDir = path.dirname(fileName); | ||
const resolvedPath = resolvePath(baseDir, sourcePath); | ||
const normalizedPath = normalizePath(resolvedPath); | ||
source.replaceWith(types.stringLiteral(normalizedPath)); | ||
} | ||
|
||
function resolvePath(baseDir, sourcePath) { | ||
for (const title of [sourcePath, path.join(sourcePath, 'index')]) { | ||
const resolvedPath = resolveExtension(baseDir, title); | ||
if (resolvedPath !== null) { | ||
return resolvedPath; | ||
} | ||
} | ||
throw new Error(`local import for "${sourcePath}" could not be resolved`); | ||
} | ||
|
||
function resolveExtension(baseDir, sourcePath) { | ||
const absolutePath = path.join(baseDir, sourcePath); | ||
if (isFile(absolutePath)) { | ||
return sourcePath; | ||
} | ||
for (const extension of srcExtensions) { | ||
if (isFile(`${absolutePath}${extension}`)) { | ||
return path.relative(baseDir, `${absolutePath}${dstExtension}`); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
function normalizePath(originalPath) { | ||
let normalizedPath = originalPath; | ||
if (path.sep === '\\') { | ||
normalizedPath = normalizedPath.split(path.sep).join('/'); | ||
} | ||
if (normalizedPath[0] !== '.') { | ||
normalizedPath = `./${normalizedPath}`; | ||
} | ||
return normalizedPath; | ||
} | ||
|
||
function isFile(pathName) { | ||
try { | ||
return fs.statSync(pathName).isFile(); | ||
} catch (err) { | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.