Skip to content

Commit

Permalink
feat: add support for jsxImportSource, new JSX transform
Browse files Browse the repository at this point in the history
  • Loading branch information
Nate Moore committed Jul 20, 2021
1 parent b9c5b7e commit 077c4bf
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 12 deletions.
6 changes: 6 additions & 0 deletions Brewfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
tap "homebrew/bundle"
tap "homebrew/core"
brew "fnm"
brew "fzf"
brew "gh"
brew "git"
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { h, Fragment } from 'preact';
import { useState } from 'preact/hooks';

/** a counter written in Preact */
Expand Down
12 changes: 12 additions & 0 deletions examples/framework-multiple/src/components/PreactSFC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @jsxImportSource preact */

/** a counter written in Preact */
export default function PreactSFC({ children }) {
return (
<>
<div className="counter">
Hello from Preact!
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';

/** a counter written in React */
export function Counter({ children }) {
Expand Down
3 changes: 3 additions & 0 deletions examples/framework-multiple/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { A, B as Renamed } from '../components';
import * as react from '../components/ReactCounter.jsx';
import { PreactCounter } from '../components/PreactCounter.tsx';
import PreactSFC from '../components/PreactSFC.tsx';
import VueCounter from '../components/VueCounter.vue';
import SvelteCounter from '../components/SvelteCounter.svelte';
Expand Down Expand Up @@ -45,6 +46,8 @@ import SvelteCounter from '../components/SvelteCounter.svelte';
<h1>Hello Preact!</h1>
</PreactCounter>

<PreactSFC />

<VueCounter client:visible>
<h1>Hello Vue!</h1>
</VueCounter>
Expand Down
5 changes: 4 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
".": "./astro.cjs",
"./package.json": "./package.json",
"./snowpack-plugin": "./snowpack-plugin.cjs",
"./snowpack-plugin-jsx": "./snowpack-plugin-jsx.cjs",
"./components": "./components/index.js",
"./components/*": "./components/*",
"./runtime/svelte": "./dist/frontend/runtime/svelte.js",
Expand Down Expand Up @@ -49,8 +50,10 @@
"@astrojs/renderer-svelte": "0.1.1",
"@astrojs/renderer-vue": "0.1.3",
"@babel/code-frame": "^7.12.13",
"@babel/core": "^7.14.6",
"@babel/generator": "^7.13.9",
"@babel/parser": "^7.13.15",
"@babel/plugin-transform-react-jsx": "^7.14.5",
"@babel/traverse": "^7.13.15",
"@snowpack/plugin-postcss": "^1.4.3",
"@snowpack/plugin-sass": "^1.4.0",
Expand All @@ -62,7 +65,7 @@
"ci-info": "^3.2.0",
"del": "^6.0.0",
"es-module-lexer": "^0.4.1",
"esbuild": "^0.10.1",
"esbuild": "^0.12.12",
"estree-util-value-to-estree": "^1.2.0",
"estree-walker": "^3.0.0",
"fast-xml-parser": "^3.19.0",
Expand Down
151 changes: 151 additions & 0 deletions packages/astro/snowpack-plugin-jsx.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const esbuild = require('esbuild');
const colors = require('kleur/colors');
const { logger } = require('snowpack');
const path = require('path');
const { promises: fs } = require('fs');

const babel = require('@babel/core')
const babelPluginTransformJsx = require('@babel/plugin-transform-react-jsx').default;
const eslexer = require('es-module-lexer');

/**
* @typedef {Object} PluginOptions - creates a new type named 'SpecialType'
* @prop {import('./src/config_manager').ConfigManager} configManager
* @prop {'development' | 'production'} mode
*/

/**
* Returns esbuild loader for a given file
* @param filePath {string}
* @returns {import('esbuild').Loader}
*/
function getLoader(filePath) {
/** @type {any} */
const ext = path.extname(filePath);
return ext.substr(1);
}

const transformJsx = ({ code, filePath: filename, sourceMap: sourceMaps }, importSource) => {
return babel.transformSync(code, {
filename,
plugins: [
babelPluginTransformJsx({}, { runtime: 'automatic', importSource })
],
sourceMaps,
}
)
}

/**
* @type {import('snowpack').SnowpackPluginFactory<PluginOptions>}
*/
module.exports = function jsxPlugin(config, options = {}) {
const {
configManager
} = options;

let didInit = false;
return {
name: '@astrojs/snowpack-plugin-jsx',
resolve: {
input: ['.jsx', '.tsx'],
output: ['.js'],
},
async load({ filePath }) {
if (!didInit) {
await eslexer.init;
didInit = true;
}

const contents = await fs.readFile(filePath, 'utf8');
const loader = getLoader(filePath);

const { code, warnings } = await esbuild.transform(contents, {
loader,
jsx: 'preserve',
sourcefile: filePath,
sourcemap: config.buildOptions.sourcemap ? 'inline' : undefined,
charset: 'utf8',
sourcesContent: config.mode !== 'production',
});
for (const warning of warnings) {
logger.error(`${colors.bold('!')} ${filePath}
${warning.text}`);
}

let renderers = await configManager.getRenderers();
const importSources = new Set(renderers.map(({ jsxImportSource }) => jsxImportSource).filter(i => i));

// If we only have a single renderer, we can skip a bunch of work!
if (importSources.size === 1) {
const result = transformJsx({
code,
filePath,
sourceMap: config.buildOptions.sourcemap ? 'inline' : false,
}, Array.from(importSources)[0])

return {
'.js': {
code: result.code || ''
},
};
}

// we need valid JS to scan for imports
// so let's just use `h` and `Fragment` as placeholders
const { code: codeToScan } = await esbuild.transform(code, {
loader: 'jsx',
jsx: 'transform',
jsxFactory: 'h',
jsxFragment: 'Fragment',
});

const [imports] = eslexer.parse(codeToScan);
let importSource;

if (imports) {
for (let { n: name } of imports) {
if (name.indexOf('/') > -1) name = name.split('/')[0];
if (importSources.has(name)) {
importSource = name;
break;
}
}
}

if (!importSource) {
let match;

while ((match = /\/\*\*(?:[^*][^/]|\s)*@jsxImportSource\s+(.+)\s*\*\//gm.exec(contents)) !== null) {
importSource = match[1].trim();
break;
}
}

if (!importSource) {
console.log(`${filePath}
Unable to resolve JSX transformer! If you have more than one renderer enabled, you should use a pragma comment.
/* jsxImportSource: preact */
`);
return {
'.js': {
code: ''
},
}
}

const result = transformJsx({
code,
filePath,
sourceMap: config.buildOptions.sourcemap ? 'inline' : false,
}, importSource)

return {
'.js': {
code: result.code || ''
},
};
},
cleanup() {},
};
}
7 changes: 7 additions & 0 deletions packages/astro/src/config_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface RendererInstance {
external: string[] | undefined;
polyfills: string[];
hydrationPolyfills: string[];
jsxImportSource?: string;
}

const CONFIG_MODULE_BASE_NAME = '__astro_config.js';
Expand Down Expand Up @@ -119,12 +120,18 @@ export class ConfigManager {
external: raw.external,
polyfills: polyfillsNormalized,
hydrationPolyfills: hydrationPolyfillsNormalized,
jsxImportSource: raw.jsxImportSource
};
});

return rendererInstances;
}

async getRenderers(): Promise<RendererInstance[]> {
const renderers = await this.buildRendererInstances();
return renderers;
}

async buildSource(contents: string): Promise<string> {
const renderers = await this.buildRendererInstances();
const rendererServerPackages = renderers.map(({ server }) => server);
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
mount: mountOptions,
mode,
plugins: [
[fileURLToPath(new URL('../snowpack-plugin-jsx.cjs', import.meta.url)), astroPluginOptions],
[fileURLToPath(new URL('../snowpack-plugin.cjs', import.meta.url)), astroPluginOptions],
...rendererSnowpackPlugins,
resolveDependency('@snowpack/plugin-sass'),
Expand Down
3 changes: 2 additions & 1 deletion packages/renderers/renderer-preact/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export default {
name: '@astrojs/renderer-preact',
client: './client',
server: './server',
knownEntrypoints: ['preact', 'preact-render-to-string'],
knownEntrypoints: ['preact', 'preact/jsx-runtime', 'preact-render-to-string'],
jsxImportSource: 'preact',
};
3 changes: 2 additions & 1 deletion packages/renderers/renderer-react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export default {
name: '@astrojs/renderer-react',
client: './client',
server: './server',
knownEntrypoints: ['react', 'react-dom', 'react-dom/server'],
knownEntrypoints: ['react', 'react/jsx-runtime', 'react-dom', 'react-dom/server'],
jsxImportSource: 'react',
};
Loading

0 comments on commit 077c4bf

Please sign in to comment.