First pass at the build (#27)
This updates `astro build` to do a production build. It works! No optimizations yet.
This commit is contained in:
parent
18e7cc5af9
commit
3db5959377
17 changed files with 186 additions and 115 deletions
1
examples/snowpack/.gitignore
vendored
1
examples/snowpack/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
|
_site
|
|
@ -245,8 +245,8 @@ export let version: string = '3.1.2';
|
||||||
<span class="logo-type">Snowpack</span>
|
<span class="logo-type">Snowpack</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<input type="text" name="search" placeholder="Search documentation..." class="search-form-input"
|
<input type="text" name="search" placeholder="Search documentation..." class="search-input"
|
||||||
id="search-input">
|
id="search-form-input">
|
||||||
<span class="search-hint">
|
<span class="search-hint">
|
||||||
<span class="sr-only">Press </span>
|
<span class="sr-only">Press </span>
|
||||||
<kbd class="font-sans"><abbr title="Command" style="text-decoration: none;">⌘</abbr></kbd>
|
<kbd class="font-sans"><abbr title="Command" style="text-decoration: none;">⌘</abbr></kbd>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
<script>
|
||||||
//let name = 'world';
|
//let name = 'world';
|
||||||
// TODO make this dynamic?
|
// TODO make this dynamic?
|
||||||
---
|
</script>
|
||||||
|
|
||||||
<h3>Assets</h3>
|
<h3>Assets</h3>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { h, Fragment } from 'preact';
|
import { h, Fragment } from 'preact';
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
import * as Styles from './PluginSearchPage.css';
|
import * as Styles from './PluginSearchPage.module.css';
|
||||||
|
|
||||||
async function searchPlugins(val) {
|
async function searchPlugins(val) {
|
||||||
const params3 = new URLSearchParams([
|
const params3 = new URLSearchParams([
|
||||||
|
|
|
@ -15,7 +15,7 @@ module.exports = {
|
||||||
out: '_site',
|
out: '_site',
|
||||||
},
|
},
|
||||||
optimize: {
|
optimize: {
|
||||||
bundle: true,
|
bundle: false,
|
||||||
minify: true,
|
minify: true,
|
||||||
target: 'es2018',
|
target: 'es2018',
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,6 @@ import type { ValidExtensionPlugins } from './astro';
|
||||||
|
|
||||||
export interface CompileOptions {
|
export interface CompileOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
resolve: (p: string) => string;
|
resolve: (p: string) => Promise<string>;
|
||||||
extensions?: Record<string, ValidExtensionPlugins>;
|
extensions?: Record<string, ValidExtensionPlugins>;
|
||||||
}
|
}
|
||||||
|
|
83
src/build.ts
Normal file
83
src/build.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import type { AstroConfig } from './@types/astro';
|
||||||
|
import { defaultLogOptions, LogOptions } from './logger';
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadConfiguration,
|
||||||
|
startServer as startSnowpackServer,
|
||||||
|
build as snowpackBuild } from 'snowpack';
|
||||||
|
import { promises as fsPromises } from 'fs';
|
||||||
|
import { relative as pathRelative } from 'path';
|
||||||
|
import { defaultLogDestination, error } from './logger.js';
|
||||||
|
import { createRuntime } from './runtime.js';
|
||||||
|
|
||||||
|
const { mkdir, readdir, stat, writeFile } = fsPromises;
|
||||||
|
|
||||||
|
const logging: LogOptions = {
|
||||||
|
level: 'debug',
|
||||||
|
dest: defaultLogDestination,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function* allPages(root: URL): AsyncGenerator<URL, void, unknown> {
|
||||||
|
for (const filename of await readdir(root)) {
|
||||||
|
const fullpath = new URL(filename, root);
|
||||||
|
const info = await stat(fullpath);
|
||||||
|
|
||||||
|
if (info.isDirectory()) {
|
||||||
|
yield* allPages(new URL(fullpath + '/'));
|
||||||
|
} else {
|
||||||
|
if(/\.(astro|md)$/.test(fullpath.pathname)) {
|
||||||
|
yield fullpath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
||||||
|
const { projectRoot, astroRoot } = astroConfig;
|
||||||
|
const pageRoot = new URL('./pages/', astroRoot);
|
||||||
|
const dist = new URL(astroConfig.dist + '/', projectRoot);
|
||||||
|
|
||||||
|
const runtimeLogging: LogOptions = {
|
||||||
|
level: 'error',
|
||||||
|
dest: defaultLogDestination
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtime = await createRuntime(astroConfig, { logging: runtimeLogging, env: 'build' });
|
||||||
|
const { snowpackConfig } = runtime.runtimeConfig;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await snowpackBuild({
|
||||||
|
config: snowpackConfig,
|
||||||
|
lockfile: null
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
error(logging, 'build', err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const filepath of allPages(pageRoot)) {
|
||||||
|
const rel = pathRelative(astroRoot.pathname + '/pages', filepath.pathname); // pages/index.astro
|
||||||
|
const pagePath = `/${rel.replace(/\.(astro|md)/, '')}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const outPath = new URL('./' + rel.replace(/\.(astro|md)/, '.html'), dist);
|
||||||
|
const outFolder = new URL('./', outPath);
|
||||||
|
const result = await runtime.load(pagePath);
|
||||||
|
|
||||||
|
if(result.statusCode !== 200) {
|
||||||
|
error(logging, 'generate', result.error || result.statusCode);
|
||||||
|
//return 1;
|
||||||
|
} else {
|
||||||
|
await mkdir(outFolder, { recursive: true });
|
||||||
|
await writeFile(outPath, result.contents, 'utf-8');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
error(logging, 'generate', err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await runtime.shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -5,10 +5,14 @@ import { promises as fsPromises } from 'fs';
|
||||||
import yargs from 'yargs-parser';
|
import yargs from 'yargs-parser';
|
||||||
|
|
||||||
import { loadConfig } from './config.js';
|
import { loadConfig } from './config.js';
|
||||||
import generate from './generate.js';
|
import {build} from './build.js';
|
||||||
import devServer from './dev.js';
|
import devServer from './dev.js';
|
||||||
|
|
||||||
const { readFile } = fsPromises;
|
const { readFile } = fsPromises;
|
||||||
|
const buildAndExit = async (...args: Parameters<typeof build>) => {
|
||||||
|
const ret = await build(...args);
|
||||||
|
process.exit(ret);
|
||||||
|
}
|
||||||
|
|
||||||
type Arguments = yargs.Arguments;
|
type Arguments = yargs.Arguments;
|
||||||
type cliState = 'help' | 'version' | 'dev' | 'build';
|
type cliState = 'help' | 'version' | 'dev' | 'build';
|
||||||
|
@ -61,7 +65,7 @@ async function runCommand(rawRoot: string, cmd: (a: AstroConfig) => Promise<void
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmdMap = new Map([
|
const cmdMap = new Map([
|
||||||
['build', generate],
|
['build', buildAndExit],
|
||||||
['dev', devServer],
|
['dev', devServer],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import babelParser from '@babel/parser';
|
||||||
import _babelGenerator from '@babel/generator';
|
import _babelGenerator from '@babel/generator';
|
||||||
import traverse from '@babel/traverse';
|
import traverse from '@babel/traverse';
|
||||||
import { ImportDeclaration,ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types';
|
import { ImportDeclaration,ExportNamedDeclaration, VariableDeclarator, Identifier, VariableDeclaration } from '@babel/types';
|
||||||
|
import { type } from 'node:os';
|
||||||
|
|
||||||
const babelGenerator: typeof _babelGenerator =
|
const babelGenerator: typeof _babelGenerator =
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -100,6 +101,7 @@ function generateAttributes(attrs: Record<string, string>): string {
|
||||||
interface ComponentInfo {
|
interface ComponentInfo {
|
||||||
type: string;
|
type: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
plugin: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = {
|
const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = {
|
||||||
|
@ -109,13 +111,14 @@ const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = {
|
||||||
'.svelte': 'svelte',
|
'.svelte': 'svelte',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compileOptions: CompileOptions) {
|
type DynamicImportMap = Map<
|
||||||
const { resolve, extensions = defaultExtensions } = compileOptions;
|
'vue' | 'react' | 'react-dom' | 'preact',
|
||||||
|
string
|
||||||
|
>;
|
||||||
|
|
||||||
|
function getComponentWrapper(_name: string, { type, plugin, url }: ComponentInfo, dynamicImports: DynamicImportMap) {
|
||||||
const [name, kind] = _name.split(':');
|
const [name, kind] = _name.split(':');
|
||||||
|
|
||||||
const plugin = extensions[type] || defaultExtensions[type];
|
|
||||||
|
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
throw new Error(`No supported plugin found for extension ${type}`);
|
throw new Error(`No supported plugin found for extension ${type}`);
|
||||||
}
|
}
|
||||||
|
@ -133,7 +136,7 @@ function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compil
|
||||||
case 'preact': {
|
case 'preact': {
|
||||||
if (kind === 'dynamic') {
|
if (kind === 'dynamic') {
|
||||||
return {
|
return {
|
||||||
wrapper: `__preact_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('preact')}')`,
|
wrapper: `__preact_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get('preact')!}')`,
|
||||||
wrapperImport: `import {__preact_dynamic} from '${internalImport('render/preact.js')}';`,
|
wrapperImport: `import {__preact_dynamic} from '${internalImport('render/preact.js')}';`,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -146,9 +149,9 @@ function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compil
|
||||||
case 'react': {
|
case 'react': {
|
||||||
if (kind === 'dynamic') {
|
if (kind === 'dynamic') {
|
||||||
return {
|
return {
|
||||||
wrapper: `__react_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve(
|
wrapper: `__react_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get(
|
||||||
'react'
|
'react'
|
||||||
)}', '${resolve('react-dom')}')`,
|
)!}', '${dynamicImports.get('react-dom')!}')`,
|
||||||
wrapperImport: `import {__react_dynamic} from '${internalImport('render/react.js')}';`,
|
wrapperImport: `import {__react_dynamic} from '${internalImport('render/react.js')}';`,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -174,7 +177,7 @@ function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compil
|
||||||
case 'vue': {
|
case 'vue': {
|
||||||
if (kind === 'dynamic') {
|
if (kind === 'dynamic') {
|
||||||
return {
|
return {
|
||||||
wrapper: `__vue_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.vue.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${resolve('vue')}')`,
|
wrapper: `__vue_dynamic(${name}, new URL(${JSON.stringify(url.replace(/\.[^.]+$/, '.vue.js'))}, \`http://TEST\${import.meta.url}\`).pathname, '${dynamicImports.get('vue')!}')`,
|
||||||
wrapperImport: `import {__vue_dynamic} from '${internalImport('render/vue.js')}';`,
|
wrapperImport: `import {__vue_dynamic} from '${internalImport('render/vue.js')}';`,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -186,23 +189,10 @@ function getComponentWrapper(_name: string, { type, url }: ComponentInfo, compil
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
default: {
|
||||||
throw new Error('Unknown Component Type: ' + name);
|
throw new Error(`Unknown component type`);
|
||||||
}
|
|
||||||
|
|
||||||
function compileScriptSafe(raw: string): string {
|
|
||||||
let compiledCode = compileExpressionSafe(raw);
|
|
||||||
// esbuild treeshakes unused imports. In our case these are components, so let's keep them.
|
|
||||||
const imports = eslexer
|
|
||||||
.parse(raw)[0]
|
|
||||||
.filter(({ d }) => d === -1)
|
|
||||||
.map((i) => raw.substring(i.ss, i.se));
|
|
||||||
for (let importStatement of imports) {
|
|
||||||
if (!compiledCode.includes(importStatement)) {
|
|
||||||
compiledCode = importStatement + '\n' + compiledCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return compiledCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileExpressionSafe(raw: string): string {
|
function compileExpressionSafe(raw: string): string {
|
||||||
|
@ -215,7 +205,30 @@ function compileExpressionSafe(raw: string): string {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function acquireDynamicComponentImports(plugins: Set<ValidExtensionPlugins>, resolve: (s: string) => Promise<string>): Promise<DynamicImportMap> {
|
||||||
|
const importMap: DynamicImportMap = new Map();
|
||||||
|
for(let plugin of plugins) {
|
||||||
|
switch(plugin) {
|
||||||
|
case 'vue': {
|
||||||
|
importMap.set('vue', await resolve('vue'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'react': {
|
||||||
|
importMap.set('react', await resolve('react'));
|
||||||
|
importMap.set('react-dom', await resolve('react-dom'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'preact': {
|
||||||
|
importMap.set('preact', await resolve('preact'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return importMap;
|
||||||
|
}
|
||||||
|
|
||||||
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> {
|
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> {
|
||||||
|
const { extensions = defaultExtensions } = compileOptions;
|
||||||
await eslexer.init;
|
await eslexer.init;
|
||||||
|
|
||||||
const componentImports: ImportDeclaration[] = [];
|
const componentImports: ImportDeclaration[] = [];
|
||||||
|
@ -225,7 +238,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
let script = '';
|
let script = '';
|
||||||
let propsStatement: string = '';
|
let propsStatement: string = '';
|
||||||
const importExportStatements: Set<string> = new Set();
|
const importExportStatements: Set<string> = new Set();
|
||||||
const components: Record<string, { type: string; url: string }> = {};
|
const components: Record<string, { type: string; url: string, plugin: string | undefined }> = {};
|
||||||
|
const componentPlugins = new Set<ValidExtensionPlugins>();
|
||||||
|
|
||||||
if (ast.module) {
|
if (ast.module) {
|
||||||
const program = babelParser.parse(ast.module.content, {
|
const program = babelParser.parse(ast.module.content, {
|
||||||
|
@ -259,7 +273,15 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
const importUrl = componentImport.source.value;
|
const importUrl = componentImport.source.value;
|
||||||
const componentType = path.posix.extname(importUrl);
|
const componentType = path.posix.extname(importUrl);
|
||||||
const componentName = path.posix.basename(importUrl, componentType);
|
const componentName = path.posix.basename(importUrl, componentType);
|
||||||
components[componentName] = { type: componentType, url: importUrl };
|
const plugin = extensions[componentType] || defaultExtensions[componentType];
|
||||||
|
components[componentName] = {
|
||||||
|
type: componentType,
|
||||||
|
plugin,
|
||||||
|
url: importUrl
|
||||||
|
};
|
||||||
|
if(plugin) {
|
||||||
|
componentPlugins.add(plugin);
|
||||||
|
}
|
||||||
importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!));
|
importExportStatements.add(ast.module.content.slice(componentImport.start!, componentImport.end!));
|
||||||
}
|
}
|
||||||
for (const componentImport of componentExports) {
|
for (const componentImport of componentExports) {
|
||||||
|
@ -280,6 +302,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
script = propsStatement + babelGenerator(program).code;
|
script = propsStatement + babelGenerator(program).code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dynamicImports = await acquireDynamicComponentImports(componentPlugins, compileOptions.resolve);
|
||||||
|
|
||||||
let items: JsxItem[] = [];
|
let items: JsxItem[] = [];
|
||||||
let collectionItem: JsxItem | undefined;
|
let collectionItem: JsxItem | undefined;
|
||||||
let currentItemName: string | undefined;
|
let currentItemName: string | undefined;
|
||||||
|
@ -304,7 +328,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
if (!components[componentName]) {
|
if (!components[componentName]) {
|
||||||
throw new Error(`Unknown Component: ${componentName}`);
|
throw new Error(`Unknown Component: ${componentName}`);
|
||||||
}
|
}
|
||||||
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
|
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], dynamicImports);
|
||||||
if (wrapperImport) {
|
if (wrapperImport) {
|
||||||
importExportStatements.add(wrapperImport);
|
importExportStatements.add(wrapperImport);
|
||||||
}
|
}
|
||||||
|
@ -356,7 +380,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
|
||||||
if (!componentImportData) {
|
if (!componentImportData) {
|
||||||
throw new Error(`Unknown Component: ${componentName}`);
|
throw new Error(`Unknown Component: ${componentName}`);
|
||||||
}
|
}
|
||||||
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], compileOptions);
|
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], dynamicImports);
|
||||||
if (wrapperImport) {
|
if (wrapperImport) {
|
||||||
importExportStatements.add(wrapperImport);
|
importExportStatements.add(wrapperImport);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ import { codegen } from './codegen.js';
|
||||||
|
|
||||||
interface CompileOptions {
|
interface CompileOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
resolve: (p: string) => string;
|
resolve: (p: string) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultCompileOptions: CompileOptions = {
|
const defaultCompileOptions: CompileOptions = {
|
||||||
logging: defaultLogOptions,
|
logging: defaultLogOptions,
|
||||||
resolve: (p: string) => p,
|
resolve: (p: string) => Promise.resolve(p),
|
||||||
};
|
};
|
||||||
|
|
||||||
function internalImport(internalPath: string) {
|
function internalImport(internalPath: string) {
|
||||||
|
|
|
@ -73,7 +73,13 @@ export async function optimize(ast: Ast, opts: OptimizeOptions) {
|
||||||
const cssVisitors = createVisitorCollection();
|
const cssVisitors = createVisitorCollection();
|
||||||
const finalizers: Array<() => Promise<void>> = [];
|
const finalizers: Array<() => Promise<void>> = [];
|
||||||
|
|
||||||
collectVisitors(optimizeStyles(opts), htmlVisitors, cssVisitors, finalizers);
|
const optimizers = [
|
||||||
|
optimizeStyles(opts)
|
||||||
|
];
|
||||||
|
|
||||||
|
for(const optimizer of optimizers) {
|
||||||
|
collectVisitors(optimizer, htmlVisitors, cssVisitors, finalizers);
|
||||||
|
}
|
||||||
|
|
||||||
walkAstWithVisitors(ast.css, cssVisitors);
|
walkAstWithVisitors(ast.css, cssVisitors);
|
||||||
walkAstWithVisitors(ast.html, htmlVisitors);
|
walkAstWithVisitors(ast.html, htmlVisitors);
|
||||||
|
|
|
@ -21,7 +21,7 @@ const logging: LogOptions = {
|
||||||
export default async function (astroConfig: AstroConfig) {
|
export default async function (astroConfig: AstroConfig) {
|
||||||
const { projectRoot } = astroConfig;
|
const { projectRoot } = astroConfig;
|
||||||
|
|
||||||
const runtime = await createRuntime(astroConfig, logging);
|
const runtime = await createRuntime(astroConfig, { logging, env: 'dev' });
|
||||||
|
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
const result = await runtime.load(req.url);
|
const result = await runtime.load(req.url);
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import render from 'preact-render-to-string';
|
import renderToString from 'preact-render-to-string';
|
||||||
import { h } from 'preact';
|
import { h, render } from 'preact';
|
||||||
import type { Component } from 'preact';
|
import type { Component } from 'preact';
|
||||||
|
|
||||||
|
// This prevents tree-shaking of render.
|
||||||
|
Function.prototype(render);
|
||||||
|
|
||||||
export function __preact_static(PreactComponent: Component) {
|
export function __preact_static(PreactComponent: Component) {
|
||||||
return (attrs: Record<string, any>, ...children: any): string => {
|
return (attrs: Record<string, any>, ...children: any): string => {
|
||||||
let html = render(
|
let html = renderToString(
|
||||||
h(
|
h(
|
||||||
PreactComponent as any, // Preact's types seem wrong...
|
PreactComponent as any, // Preact's types seem wrong...
|
||||||
attrs,
|
attrs,
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
import type { AstroConfig } from './@types/astro';
|
|
||||||
import { loadConfiguration, startServer as startSnowpackServer } from 'snowpack';
|
|
||||||
import { promises as fsPromises } from 'fs';
|
|
||||||
import { relative as pathRelative } from 'path';
|
|
||||||
|
|
||||||
const { mkdir, readdir, stat, writeFile } = fsPromises;
|
|
||||||
|
|
||||||
async function* allPages(root: URL): AsyncGenerator<URL, void, unknown> {
|
|
||||||
for (const filename of await readdir(root)) {
|
|
||||||
const fullpath = new URL(filename, root);
|
|
||||||
const info = await stat(fullpath);
|
|
||||||
|
|
||||||
if (info.isDirectory()) {
|
|
||||||
yield* allPages(new URL(fullpath + '/'));
|
|
||||||
} else {
|
|
||||||
yield fullpath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function (astroConfig: AstroConfig) {
|
|
||||||
const { projectRoot, astroRoot } = astroConfig;
|
|
||||||
const pageRoot = new URL('./pages/', astroRoot);
|
|
||||||
const dist = new URL(astroConfig.dist + '/', projectRoot);
|
|
||||||
|
|
||||||
const configPath = new URL('./snowpack.config.js', projectRoot).pathname;
|
|
||||||
const config = await loadConfiguration(
|
|
||||||
{
|
|
||||||
root: projectRoot.pathname,
|
|
||||||
devOptions: { open: 'none', output: 'stream' },
|
|
||||||
},
|
|
||||||
configPath
|
|
||||||
);
|
|
||||||
const snowpack = await startSnowpackServer({
|
|
||||||
config,
|
|
||||||
lockfile: null, // TODO should this be required?
|
|
||||||
});
|
|
||||||
|
|
||||||
const runtime = snowpack.getServerRuntime();
|
|
||||||
|
|
||||||
for await (const filepath of allPages(pageRoot)) {
|
|
||||||
const rel = pathRelative(astroRoot.pathname, filepath.pathname); // pages/index.astro
|
|
||||||
const pagePath = `/_astro/${rel.replace(/\.(astro|md)/, '.js')}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const outPath = new URL('./' + rel.replace(/\.(astro|md)/, '.html'), dist);
|
|
||||||
const outFolder = new URL('./', outPath);
|
|
||||||
const mod = await runtime.importModule(pagePath);
|
|
||||||
const html = await mod.exports.default({});
|
|
||||||
|
|
||||||
await mkdir(outFolder, { recursive: true });
|
|
||||||
await writeFile(outPath, html, 'utf-8');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Unable to generate page', rel);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await snowpack.shutdown();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { parse_expression_at } from '../acorn.js';
|
import { parse_expression_at } from '../acorn.js';
|
||||||
import { Parser } from '../index.js';
|
import { Parser } from '../index.js';
|
||||||
import { whitespace } from '../../utils/patterns.js';
|
import { whitespace } from '../../utils/patterns.js';
|
||||||
// import { Node } from 'estree';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export default function read_expression(parser: Parser): string {
|
export default function read_expression(parser: Parser): string {
|
||||||
|
@ -35,7 +34,6 @@ export default function read_expression(parser: Parser): string {
|
||||||
parser.index = index;
|
parser.index = index;
|
||||||
|
|
||||||
return parser.template.substring(start, index);
|
return parser.template.substring(start, index);
|
||||||
// return node as Node;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
parser.acorn_error(err);
|
parser.acorn_error(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult } from 'snowpack';
|
import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadResult as SnowpackLoadResult, SnowpackConfig } from 'snowpack';
|
||||||
import type { AstroConfig } from './@types/astro';
|
import type { AstroConfig } from './@types/astro';
|
||||||
import type { LogOptions } from './logger';
|
import type { LogOptions } from './logger';
|
||||||
import type { CompileError } from './parser/utils/error.js';
|
import type { CompileError } from './parser/utils/error.js';
|
||||||
|
@ -14,6 +14,7 @@ interface RuntimeConfig {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
snowpack: SnowpackDevServer;
|
snowpack: SnowpackDevServer;
|
||||||
snowpackRuntime: SnowpackServerRuntime;
|
snowpackRuntime: SnowpackServerRuntime;
|
||||||
|
snowpackConfig: SnowpackConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoadResultSuccess = {
|
type LoadResultSuccess = {
|
||||||
|
@ -96,24 +97,34 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRuntime(astroConfig: AstroConfig, logging: LogOptions) {
|
interface RuntimeOptions {
|
||||||
|
logging: LogOptions;
|
||||||
|
env: 'dev' | 'build'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRuntime(astroConfig: AstroConfig, { env, logging }: RuntimeOptions) {
|
||||||
const { projectRoot, astroRoot, extensions } = astroConfig;
|
const { projectRoot, astroRoot, extensions } = astroConfig;
|
||||||
|
|
||||||
const internalPath = new URL('./frontend/', import.meta.url);
|
const internalPath = new URL('./frontend/', import.meta.url);
|
||||||
|
|
||||||
// Workaround for SKY-251
|
let snowpack: SnowpackDevServer;
|
||||||
const astroPlugOptions: {
|
const astroPlugOptions: {
|
||||||
resolve?: (s: string) => string;
|
resolve?: (s: string) => Promise<string>;
|
||||||
extensions?: Record<string, string>;
|
extensions?: Record<string, string>;
|
||||||
} = { extensions };
|
} = {
|
||||||
if (existsSync(new URL('./package-lock.json', projectRoot))) {
|
extensions,
|
||||||
|
resolve: env === 'dev' ?
|
||||||
|
async (pkgName: string) => snowpack.getUrlForPackage(pkgName) :
|
||||||
|
async (pkgName: string) => `/_snowpack/pkg/${pkgName}.js`
|
||||||
|
};
|
||||||
|
/*if (existsSync(new URL('./package-lock.json', projectRoot))) {
|
||||||
const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8');
|
const pkgLockStr = await readFile(new URL('./package-lock.json', projectRoot), 'utf-8');
|
||||||
const pkgLock = JSON.parse(pkgLockStr);
|
const pkgLock = JSON.parse(pkgLockStr);
|
||||||
astroPlugOptions.resolve = (pkgName: string) => {
|
astroPlugOptions.resolve = (pkgName: string) => {
|
||||||
const ver = pkgLock.dependencies[pkgName].version;
|
const ver = pkgLock.dependencies[pkgName].version;
|
||||||
return `/_snowpack/pkg/${pkgName}.v${ver}.js`;
|
return `/_snowpack/pkg/${pkgName}.v${ver}.js`;
|
||||||
};
|
};
|
||||||
}
|
}*/
|
||||||
|
|
||||||
const snowpackConfig = await loadConfiguration({
|
const snowpackConfig = await loadConfiguration({
|
||||||
root: projectRoot.pathname,
|
root: projectRoot.pathname,
|
||||||
|
@ -132,7 +143,7 @@ export async function createRuntime(astroConfig: AstroConfig, logging: LogOption
|
||||||
external: ['@vue/server-renderer', 'node-fetch'],
|
external: ['@vue/server-renderer', 'node-fetch'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const snowpack = await startSnowpackServer({
|
snowpack = await startSnowpackServer({
|
||||||
config: snowpackConfig,
|
config: snowpackConfig,
|
||||||
lockfile: null,
|
lockfile: null,
|
||||||
});
|
});
|
||||||
|
@ -143,9 +154,11 @@ export async function createRuntime(astroConfig: AstroConfig, logging: LogOption
|
||||||
logging,
|
logging,
|
||||||
snowpack,
|
snowpack,
|
||||||
snowpackRuntime,
|
snowpackRuntime,
|
||||||
|
snowpackConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
runtimeConfig,
|
||||||
load: load.bind(null, runtimeConfig),
|
load: load.bind(null, runtimeConfig),
|
||||||
shutdown: () => snowpack.shutdown(),
|
shutdown: () => snowpack.shutdown(),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue