Allow usage of node builtins through node: prefix (#520)
* Start of allowing node builtins issue * Allow use of node:builtin * Produce an error in Astro files with bare builtin usage * Upgrade snowpack version bug fixes for packages that use `node:` * Document node builtins * Use the provided builtins list
This commit is contained in:
parent
3f3e4f1286
commit
e316c9578c
16 changed files with 136 additions and 7 deletions
19
docs/api.md
19
docs/api.md
|
@ -174,3 +174,22 @@ export default function () {
|
|||
[config]: ../README.md#%EF%B8%8F-configuration
|
||||
[docs-collections]: ./collections.md
|
||||
[rss]: #-rss-feed
|
||||
|
||||
### Node builtins
|
||||
|
||||
Astro aims to be compatible with multiple JavaScript runtimes in the future. This includes [Deno](https://deno.land/) and [Cloudflare Workers](https://workers.cloudflare.com/) which do not support Node builtin modules such as `fs`. We encourage Astro users to write their code as cross-environment as possible.
|
||||
|
||||
Due to that, you cannot use Node modules that you're familiar with such as `fs` and `path`. Our aim is to provide alternative built in to Astro. If you're use case is not covered please let us know.
|
||||
|
||||
However, if you *really* need to use these builtin modules we don't want to stop you. Node supports the `node:` prefix for importing builtins, and this is also supported by Astro. If you want to read a file, for example, you can do so like this:
|
||||
|
||||
```jsx
|
||||
---
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
const url = new URL('../../package.json', import.meta.url);
|
||||
const json = await fs.readFile(url, 'utf-8');
|
||||
const data = JSON.parse(json);
|
||||
---
|
||||
|
||||
<span>Version: {data.version}</span>
|
|
@ -28,7 +28,8 @@
|
|||
"tools/*",
|
||||
"scripts",
|
||||
"www",
|
||||
"docs-www"
|
||||
"docs-www",
|
||||
"packages/astro/test/fixtures/builtins/packages/*"
|
||||
],
|
||||
"volta": {
|
||||
"node": "14.16.1",
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
"sass": "^1.32.13",
|
||||
"shorthash": "^0.0.2",
|
||||
"slash": "^4.0.0",
|
||||
"snowpack": "^3.6.0",
|
||||
"snowpack": "^3.6.1",
|
||||
"source-map-support": "^0.5.19",
|
||||
"string-width": "^5.0.0",
|
||||
"tiny-glob": "^0.2.8",
|
||||
|
|
|
@ -22,6 +22,7 @@ import { renderMarkdown } from '@astrojs/markdown-support';
|
|||
import { transform } from '../transform/index.js';
|
||||
import { PRISM_IMPORT } from '../transform/prism.js';
|
||||
import { positionAt } from '../utils';
|
||||
import { nodeBuiltinsSet } from '../../node_builtins.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
|
||||
|
@ -327,6 +328,9 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
|
||||
for (const componentImport of componentImports) {
|
||||
const importUrl = componentImport.source.value;
|
||||
if(nodeBuiltinsSet.has(importUrl)) {
|
||||
throw new Error(`Node builtins must be prefixed with 'node:'. Use node:${importUrl} instead.`);
|
||||
}
|
||||
for (const specifier of componentImport.specifiers) {
|
||||
const componentName = specifier.local.name;
|
||||
state.components.set(componentName, {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createRequire } from 'module';
|
||||
import { nodeBuiltinsMap } from './node_builtins.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkg = require('../package.json');
|
||||
|
||||
|
@ -17,7 +18,13 @@ const isAstroRenderer = (name: string) => {
|
|||
|
||||
// These packages should NOT be built by `esinstall`
|
||||
// But might not be explicit dependencies of `astro`
|
||||
const denyList = ['prismjs/components/index.js', '@vue/server-renderer', '@astrojs/markdown-support'];
|
||||
const denyList = [
|
||||
'prismjs/components/index.js',
|
||||
'@vue/server-renderer',
|
||||
'@astrojs/markdown-support',
|
||||
'node:fs/promises',
|
||||
...nodeBuiltinsMap.values()
|
||||
];
|
||||
|
||||
export default Object.keys(pkg.dependencies)
|
||||
// Filter out packages that should be loaded threw Snowpack
|
||||
|
|
5
packages/astro/src/node_builtins.ts
Normal file
5
packages/astro/src/node_builtins.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
import {builtinModules} from 'module';
|
||||
|
||||
export const nodeBuiltinsSet = new Set(builtinModules);
|
||||
export const nodeBuiltinsMap = new Map(builtinModules.map(bareName => [bareName, 'node:' + bareName]));
|
|
@ -22,6 +22,7 @@ import { debug, info } from './logger.js';
|
|||
import { configureSnowpackLogger } from './snowpack-logger.js';
|
||||
import { searchForPage } from './search.js';
|
||||
import snowpackExternals from './external.js';
|
||||
import { nodeBuiltinsMap } from './node_builtins.js';
|
||||
import { ConfigManager } from './config_manager.js';
|
||||
|
||||
interface RuntimeConfig {
|
||||
|
@ -389,6 +390,9 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
|
|||
knownEntrypoints,
|
||||
external: snowpackExternals,
|
||||
},
|
||||
alias: {
|
||||
...Object.fromEntries(nodeBuiltinsMap)
|
||||
}
|
||||
});
|
||||
|
||||
snowpack = await startSnowpackServer(
|
||||
|
|
26
packages/astro/test/builtins.test.js
Normal file
26
packages/astro/test/builtins.test.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { suite } from 'uvu';
|
||||
import * as assert from 'uvu/assert';
|
||||
import { doc } from './test-utils.js';
|
||||
import { setup } from './helpers.js';
|
||||
|
||||
const Builtins = suite('Node builtins');
|
||||
|
||||
setup(Builtins, './fixtures/builtins');
|
||||
|
||||
Builtins('Can be used with the node: prefix', async ({ runtime }) => {
|
||||
const result = await runtime.load('/');
|
||||
if (result.error) throw new Error(result.error);
|
||||
|
||||
const $ = doc(result.contents);
|
||||
|
||||
assert.equal($('#version').text(), '1.2.0');
|
||||
assert.equal($('#dep-version').text(), '0.0.1');
|
||||
});
|
||||
|
||||
Builtins('Throw if using the non-prefixed version', async ({ runtime }) => {
|
||||
const result = await runtime.load('/bare');
|
||||
assert.ok(result.error, 'Produced an error');
|
||||
assert.ok(/Use node:fs instead/.test(result.error.message));
|
||||
});
|
||||
|
||||
Builtins.run();
|
7
packages/astro/test/fixtures/builtins/package.json
vendored
Normal file
7
packages/astro/test/fixtures/builtins/package.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@astrojs/astro-test-builtins",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@astrojs/astro-test-builtins-dep": "file:./packages/dep"
|
||||
}
|
||||
}
|
10
packages/astro/test/fixtures/builtins/packages/dep/main.js
vendored
Normal file
10
packages/astro/test/fixtures/builtins/packages/dep/main.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
import fs from 'fs';
|
||||
|
||||
const readFile = fs.promises.readFile;
|
||||
|
||||
export async function readJson(path) {
|
||||
const json = await readFile(path, 'utf-8');
|
||||
const data = JSON.parse(json);
|
||||
return data;
|
||||
}
|
||||
|
6
packages/astro/test/fixtures/builtins/packages/dep/package.json
vendored
Normal file
6
packages/astro/test/fixtures/builtins/packages/dep/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@astrojs/astro-test-builtins-dep",
|
||||
"version": "0.0.1",
|
||||
"module": "main.js",
|
||||
"main": "main.js"
|
||||
}
|
3
packages/astro/test/fixtures/builtins/snowpack.config.json
vendored
Normal file
3
packages/astro/test/fixtures/builtins/snowpack.config.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"workspaceRoot": "../../../../../"
|
||||
}
|
9
packages/astro/test/fixtures/builtins/src/components/Version.astro
vendored
Normal file
9
packages/astro/test/fixtures/builtins/src/components/Version.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
const url = new URL('../../package.json', import.meta.url);
|
||||
const json = await fs.readFile(url, 'utf-8');
|
||||
const data = JSON.parse(json);
|
||||
---
|
||||
|
||||
<span id="version">{data.version}</span>
|
12
packages/astro/test/fixtures/builtins/src/pages/bare.astro
vendored
Normal file
12
packages/astro/test/fixtures/builtins/src/pages/bare.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
import fs from 'fs';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>This should throw</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test</h1>
|
||||
</body>
|
||||
</html>
|
16
packages/astro/test/fixtures/builtins/src/pages/index.astro
vendored
Normal file
16
packages/astro/test/fixtures/builtins/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
import Version from '../components/Version.astro';
|
||||
import { readJson } from '@astrojs/astro-test-builtins-dep';
|
||||
|
||||
const depPath = new URL('../../packages/dep/package.json', import.meta.url);
|
||||
|
||||
const title = 'My App';
|
||||
const depVersion = (await readJson(depPath)).version;
|
||||
---
|
||||
|
||||
<title>{title}</title>
|
||||
|
||||
<h1>{title}</h1>
|
||||
|
||||
<Version />
|
||||
<span id="dep-version">{depVersion}</span>
|
|
@ -8940,10 +8940,10 @@ smartwrap@^1.2.3:
|
|||
wcwidth "^1.0.1"
|
||||
yargs "^15.1.0"
|
||||
|
||||
snowpack@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/snowpack/-/snowpack-3.6.0.tgz#4c1af4c760be88b1c65594f0fb90f57c99c2338d"
|
||||
integrity sha512-MCRkA3+vJTBxVtb2nwoHETMunzo96l10VsgUuxHXvxsFaZqAkdT50bViuEyFv6fhEujMh55oSHY9pCrxGYA0aQ==
|
||||
snowpack@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/snowpack/-/snowpack-3.6.1.tgz#5ae64e012deebcafca00bede6a0bb5d81dc883a6"
|
||||
integrity sha512-XS+zJIuWxAEYuni3iZqm7a0LDNhPMEfNvT0xf/aGGxWptILOzqYOGaj9nogrQc+una1vlraBwSgzB3zNwV2G5A==
|
||||
dependencies:
|
||||
cli-spinners "^2.5.0"
|
||||
default-browser-id "^2.0.0"
|
||||
|
|
Loading…
Reference in a new issue