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:
Matthew Phillips 2021-06-23 16:01:32 -04:00 committed by GitHub
parent 3f3e4f1286
commit e316c9578c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 136 additions and 7 deletions

View file

@ -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>

View file

@ -28,7 +28,8 @@
"tools/*",
"scripts",
"www",
"docs-www"
"docs-www",
"packages/astro/test/fixtures/builtins/packages/*"
],
"volta": {
"node": "14.16.1",

View file

@ -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",

View file

@ -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, {

View file

@ -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

View 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]));

View file

@ -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(

View 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();

View file

@ -0,0 +1,7 @@
{
"name": "@astrojs/astro-test-builtins",
"version": "1.2.0",
"dependencies": {
"@astrojs/astro-test-builtins-dep": "file:./packages/dep"
}
}

View 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;
}

View file

@ -0,0 +1,6 @@
{
"name": "@astrojs/astro-test-builtins-dep",
"version": "0.0.1",
"module": "main.js",
"main": "main.js"
}

View file

@ -0,0 +1,3 @@
{
"workspaceRoot": "../../../../../"
}

View 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>

View file

@ -0,0 +1,12 @@
---
import fs from 'fs';
---
<html>
<head>
<title>This should throw</title>
</head>
<body>
<h1>Test</h1>
</body>
</html>

View 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>

View file

@ -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"