Skip to content

Commit

Permalink
feat: optimize plugin organization
Browse files Browse the repository at this point in the history
  • Loading branch information
sanyuan0704 committed Sep 15, 2022
1 parent 0494133 commit 235d4bc
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 300 deletions.
90 changes: 90 additions & 0 deletions src/node/plugin-island/config.ts
@@ -0,0 +1,90 @@
import {
CLIENT_EXPORTS_PATH,
CLIENT_RUNTIME_PATH,
DEFAULT_EXTERNALS,
DEFAULT_THEME_PATH,
ISLAND_CLI_PATH,
ISLAND_JSX_RUNTIME_PATH,
isProduction,
PACKAGE_ROOT_PATH,
ROUTE_PATH
} from '../constants';
import { Plugin } from 'vite';
import { SiteConfig } from 'shared/types/index';
import { join, relative } from 'path';
import pc from 'picocolors';

const { green } = pc;

export function pluginConfig(config: SiteConfig): Plugin {
return {
name: 'island:vite-config',
// Set external
async resolveId(id) {
if (isProduction() && DEFAULT_EXTERNALS.includes(id)) {
return {
id,
external: true
};
}
},
config(c) {
return {
root: PACKAGE_ROOT_PATH,
optimizeDeps: {
include: [
'react',
'react-dom',
'react-dom/client',
'react/jsx-runtime',
'@loadable/component'
],
exclude: [
'island-ssg',
'island/theme',
'island/client',
'island/routes',
'island/jsx-runtime'
]
},
server: {
fs: {
allow: [CLIENT_RUNTIME_PATH, DEFAULT_THEME_PATH, process.cwd()]
}
},
resolve: {
alias: {
'island/theme': config.themeDir!,
'island/client': `/@fs/${CLIENT_EXPORTS_PATH}`,
'island/routes': join(c.root!, ROUTE_PATH),
'island/jsx-runtime': join(
ISLAND_JSX_RUNTIME_PATH,
'jsx-runtime.js'
)
}
},
css: {
modules: {
localsConvention: 'camelCaseOnly'
}
}
};
},
// Restart when config file changes
async handleHotUpdate(ctx) {
const customWatchedFiles = [
...(config.configDeps || []),
config.configPath,
ISLAND_CLI_PATH
];
if (customWatchedFiles.includes(ctx.file)) {
console.log(
green(
`\n${relative(config.root, ctx.file)} changed, restarting server...`
)
);
return [];
}
}
};
}
223 changes: 11 additions & 212 deletions src/node/plugin-island/index.ts
@@ -1,220 +1,19 @@
import { SiteConfig } from 'shared/types';
import type { Plugin } from 'vite';
import {
CLIENT_ENTRY_PATH,
CLIENT_RUNTIME_PATH,
DEFAULT_HTML_PATH,
isProduction,
ROUTE_PATH,
DEFAULT_THEME_PATH,
DEFAULT_EXTERNALS,
TS_REGEX,
ISLAND_JSX_RUNTIME_PATH,
PACKAGE_ROOT_PATH,
CLIENT_EXPORTS_PATH
} from '../constants';
import fs from 'fs-extra';
import { join, relative } from 'path';
import { SiteConfig } from '../../shared/types';
import { transformAsync } from '@babel/core';
import babelPluginIsland from '../babel-plugin-island';
import { transformWithEsbuild } from 'vite';
import pc from 'picocolors';
import { ISLAND_CLI_PATH } from '../constants/index';
import { pluginConfig } from './config';
import { pluginIndexHtml } from './indexHtml';
import { pluginIslandTransform } from './islandTransform';
import { pluginSiteData } from './siteDataPlugin';

const { green } = pc;

export const SITE_DATA_ID = 'island:site-data';

/**
* The plugin for island framework:
* 1. Handle module alias
* 2. Response page data
* 3. Generate html template for development
*/
export function pluginIsland(
config: SiteConfig,
isServer: boolean = false,
restartServer?: () => Promise<void>
): Plugin[] {
const { siteData } = config;
const siteDataPlugin: Plugin = {
name: 'island:site-data',
async resolveId(id) {
if (id === SITE_DATA_ID) {
return '\0' + SITE_DATA_ID;
}
if (isProduction() && DEFAULT_EXTERNALS.includes(id)) {
return {
id,
external: true
};
}
},
load(id) {
if (id === '\0' + SITE_DATA_ID) {
return `export default ${JSON.stringify(siteData)}`;
}
}
};
const internalPlugin: Plugin = {
name: 'island:vite-plugin-internal',
enforce: 'pre',
config(c) {
return {
root: PACKAGE_ROOT_PATH,
optimizeDeps: {
include: [
'react',
'react-dom',
'react-dom/client',
'react/jsx-runtime',
'@loadable/component'
],
exclude: [
'island-ssg',
'island/theme',
'island/client',
'island/routes',
'island/jsx-runtime'
]
},
server: {
fs: {
allow: [CLIENT_RUNTIME_PATH, DEFAULT_THEME_PATH, process.cwd()]
}
},
resolve: {
alias: {
'island/theme': config.themeDir!,
'island/client': `/@fs/${CLIENT_EXPORTS_PATH}`,
'island/routes': join(c.root!, ROUTE_PATH),
'island/jsx-runtime': join(
ISLAND_JSX_RUNTIME_PATH,
'jsx-runtime.js'
)
}
},
css: {
modules: {
localsConvention: 'camelCaseOnly'
}
}
};
},
async resolveId(id) {
if (isProduction() && DEFAULT_EXTERNALS.includes(id)) {
return {
id,
external: true
};
}
},
async transform(code, id, options) {
// Note: @vitejs/plugin-react cannot compile files in node_modules, so we need to compile them manually.
// In production, we should transform the __island props for collecting island components
if (
options?.ssr &&
TS_REGEX.test(id) &&
id.includes(DEFAULT_THEME_PATH)
) {
let strippedTypes = await transformWithEsbuild(code, id, {
jsx: 'preserve'
});
const result = await transformAsync((await strippedTypes).code, {
filename: id,
presets: [
[
'@babel/preset-react',
{
runtime: 'automatic',
importSource: isServer ? ISLAND_JSX_RUNTIME_PATH : 'react'
}
]
],
plugins: [babelPluginIsland]
});
return {
code: result?.code || code,
map: result?.map
};
}
},
transformIndexHtml(html) {
if (isProduction()) {
return html;
}
// Insert client entry script in development
// And in production, we will insert it in ssr render
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: `/@fs/${CLIENT_ENTRY_PATH}`
},
injectTo: 'body'
}
]
};
},
async handleHotUpdate(ctx) {
const customWatchedFiles = [
...(config.configDeps || []),
config.configPath,
ISLAND_CLI_PATH
];
if (customWatchedFiles.includes(ctx.file)) {
console.log(
green(
`\n${relative(config.root, ctx.file)} changed, restarting server...`
)
);
await restartServer!();
return [];
}

if (/\.mdx?/.test(ctx.file)) {
ctx.server.ws!.send({
type: 'custom',
event: 'md(x)-changed'
});
}
},
configureServer(server) {
if (config.configPath) {
server.watcher.add(config.configPath);
config.configDeps?.forEach((dep) => {
server.watcher.add(dep);
});
}
server.watcher.add(ISLAND_CLI_PATH);
return () => {
server.middlewares.use(async (req, res, next) => {
if (res.writableEnded) {
return next();
}
if (req.url?.replace(/\?.*/, '').endsWith('.html')) {
let html = fs.readFileSync(DEFAULT_HTML_PATH, 'utf8');

try {
html = await server.transformIndexHtml(
req.url,
html,
req.originalUrl
);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(html);
} catch (e) {
return next(e);
}
}
});
};
}
};

return [siteDataPlugin, internalPlugin];
return [
pluginSiteData(config),
pluginConfig(config),
pluginIndexHtml(config),
pluginIslandTransform(isServer)
];
}
64 changes: 64 additions & 0 deletions src/node/plugin-island/indexHtml.ts
@@ -0,0 +1,64 @@
import {
CLIENT_ENTRY_PATH,
DEFAULT_HTML_PATH,
ISLAND_CLI_PATH
} from '../constants';
import { SiteConfig } from 'shared/types/index';
import { Plugin } from 'vite';
import fs from 'fs-extra';

export function pluginIndexHtml(config: SiteConfig): Plugin {
return {
name: 'island:index-html',
apply: 'serve',
transformIndexHtml(html) {
// Insert client entry script in development
// And in production, we will insert it in ssr render
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: `/@fs/${CLIENT_ENTRY_PATH}`
},
injectTo: 'body'
}
]
};
},
configureServer(server) {
if (config.configPath) {
server.watcher.add(config.configPath);
config.configDeps?.forEach((dep) => {
server.watcher.add(dep);
});
}
server.watcher.add(ISLAND_CLI_PATH);
return () => {
server.middlewares.use(async (req, res, next) => {
if (res.writableEnded) {
return next();
}
if (req.url?.replace(/\?.*/, '').endsWith('.html')) {
let html = fs.readFileSync(DEFAULT_HTML_PATH, 'utf8');

try {
html = await server.transformIndexHtml(
req.url,
html,
req.originalUrl
);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(html);
} catch (e) {
return next(e);
}
}
});
};
}
};
}

0 comments on commit 235d4bc

Please sign in to comment.