mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Support for custom elements (#45)
* Support for custom elements Now you can use custom elements like so in Astro components: ```html <script type="module" src="./datepicker.js"> <date-picker></date-picker> ``` These will be resolve relative to the current astro component. In the build these modules are run through the same bundle/minify process as components. * Remove component from public * Formatting * Disable empty fn rule
This commit is contained in:
parent
d9084ff4ad
commit
d5b15a3851
9 changed files with 144 additions and 13 deletions
|
@ -8,6 +8,7 @@ module.exports = {
|
|||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'no-shadow': 'warn',
|
||||
'prettier/prettier': 'error',
|
||||
'prefer-const': 'off',
|
||||
|
|
|
@ -123,6 +123,10 @@ export let version: string = '3.1.2';
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> :global(.algolia-autocomplete) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-m) {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
@ -344,9 +348,5 @@ export let version: string = '3.1.2';
|
|||
}
|
||||
};
|
||||
</script>
|
||||
<script type="module" defer>
|
||||
import docsearch from 'https://cdn.skypack.dev/docsearch.js/dist/cdn/docsearch.min.js';
|
||||
docsearch({
|
||||
apiKey: '562139304880b94536fc53f5d65c5c19', indexName: 'snowpack', inputSelector: '#search-form-input', debug: true // Set debug to true if you want to inspect the dropdown
|
||||
});
|
||||
</script>
|
||||
<script type="module" src="./docsearch.js"></script>
|
||||
<doc-search api-key="562139304880b94536fc53f5d65c5c19" selector="#search-form-input"></doc-search>
|
||||
|
|
17
examples/snowpack/astro/components/docsearch.js
Normal file
17
examples/snowpack/astro/components/docsearch.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import docsearch from 'docsearch.js/dist/cdn/docsearch.min.js';
|
||||
|
||||
customElements.define('doc-search', class extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if(!this._setup) {
|
||||
const apiKey = this.getAttribute('api-key');
|
||||
const selector = this.getAttribute('selector');
|
||||
docsearch({
|
||||
apiKey: apiKey,
|
||||
indexName: 'snowpack',
|
||||
inputSelector: selector,
|
||||
debug: true // Set debug to true if you want to inspect the dropdown
|
||||
});
|
||||
this._setup = true;
|
||||
}
|
||||
}
|
||||
});
|
23
src/ast.ts
Normal file
23
src/ast.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { Attribute } from './parser/interfaces';
|
||||
|
||||
// AST utility functions
|
||||
|
||||
export function getAttr(attributes: Attribute[], name: string): Attribute | undefined {
|
||||
const attr = attributes.find((a) => a.name === name);
|
||||
return attr;
|
||||
}
|
||||
|
||||
export function getAttrValue(attributes: Attribute[], name: string): string | undefined {
|
||||
const attr = getAttr(attributes, name);
|
||||
if (attr) {
|
||||
return attr.value[0]?.data;
|
||||
}
|
||||
}
|
||||
|
||||
export function setAttrValue(attributes: Attribute[], name: string, value: string): void {
|
||||
const attr = attributes.find((a) => a.name === name);
|
||||
if (attr) {
|
||||
attr.value[0]!.data = value;
|
||||
attr.value[0]!.raw = value;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ async function writeResult(result: LoadResult, outPath: URL, encoding: null | 'u
|
|||
export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
||||
const { projectRoot, astroRoot } = astroConfig;
|
||||
const pageRoot = new URL('./pages/', astroRoot);
|
||||
const componentRoot = new URL('./components/', astroRoot);
|
||||
const dist = new URL(astroConfig.dist + '/', projectRoot);
|
||||
|
||||
const runtimeLogging: LogOptions = {
|
||||
|
@ -66,6 +67,7 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
|
||||
const imports = new Set<string>();
|
||||
const statics = new Set<string>();
|
||||
const collectImportsOptions = { astroConfig, logging, resolve };
|
||||
|
||||
for (const pathname of await allPages(pageRoot)) {
|
||||
const filepath = new URL(`file://${pathname}`);
|
||||
|
@ -90,7 +92,11 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
|
|||
return 1;
|
||||
}
|
||||
|
||||
mergeSet(imports, await collectDynamicImports(filepath, astroConfig, resolve));
|
||||
mergeSet(imports, await collectDynamicImports(filepath, collectImportsOptions));
|
||||
}
|
||||
|
||||
for (const pathname of await allPages(componentRoot)) {
|
||||
mergeSet(imports, await collectDynamicImports(new URL(`file://${pathname}`), collectImportsOptions));
|
||||
}
|
||||
|
||||
await bundle(imports, { dist, runtime, astroConfig });
|
||||
|
|
|
@ -2,10 +2,13 @@ import type { AstroConfig, ValidExtensionPlugins } from '../@types/astro';
|
|||
import type { ImportDeclaration } from '@babel/types';
|
||||
import type { InputOptions, OutputOptions } from 'rollup';
|
||||
import type { AstroRuntime } from '../runtime';
|
||||
import type { LogOptions } from '../logger';
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import { parse } from '../parser/index.js';
|
||||
import { optimize } from '../compiler/optimize/index.js';
|
||||
import { getAttrValue, setAttrValue } from '../ast.js';
|
||||
import { walk } from 'estree-walker';
|
||||
import babelParser from '@babel/parser';
|
||||
import path from 'path';
|
||||
|
@ -55,11 +58,17 @@ const defaultExtensions: Readonly<Record<string, ValidExtensionPlugins>> = {
|
|||
'.vue': 'vue',
|
||||
};
|
||||
|
||||
export async function collectDynamicImports(filename: URL, astroConfig: AstroConfig, resolve: (s: string) => Promise<string>) {
|
||||
interface CollectDynamic {
|
||||
astroConfig: AstroConfig;
|
||||
resolve: (s: string) => Promise<string>;
|
||||
logging: LogOptions;
|
||||
}
|
||||
|
||||
export async function collectDynamicImports(filename: URL, { astroConfig, logging, resolve }: CollectDynamic) {
|
||||
const imports = new Set<string>();
|
||||
|
||||
// No markdown for now
|
||||
if (filename.pathname.endsWith('md')) {
|
||||
// Only astro files
|
||||
if (!filename.pathname.endsWith('astro')) {
|
||||
return imports;
|
||||
}
|
||||
|
||||
|
@ -73,6 +82,16 @@ export async function collectDynamicImports(filename: URL, astroConfig: AstroCon
|
|||
return imports;
|
||||
}
|
||||
|
||||
await optimize(ast, {
|
||||
filename: filename.pathname,
|
||||
fileID: '',
|
||||
compileOptions: {
|
||||
astroConfig,
|
||||
resolve,
|
||||
logging,
|
||||
},
|
||||
});
|
||||
|
||||
const componentImports: ImportDeclaration[] = [];
|
||||
const components: Record<string, { plugin: ValidExtensionPlugins; type: string; specifier: string }> = {};
|
||||
const plugins = new Set<ValidExtensionPlugins>();
|
||||
|
@ -145,6 +164,18 @@ export async function collectDynamicImports(filename: URL, astroConfig: AstroCon
|
|||
walk(ast.html, {
|
||||
enter(node) {
|
||||
switch (node.type) {
|
||||
case 'Element': {
|
||||
if (node.name !== 'script') return;
|
||||
if (getAttrValue(node.attributes, 'type') !== 'module') return;
|
||||
|
||||
const src = getAttrValue(node.attributes, 'src');
|
||||
|
||||
if (src && src.startsWith('/')) {
|
||||
imports.add(src);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MustacheTag': {
|
||||
let code: string;
|
||||
try {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { walk } from 'estree-walker';
|
||||
import type { Ast, TemplateNode } from '../../parser/interfaces';
|
||||
import { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer';
|
||||
import type { CompileOptions } from '../../@types/compiler';
|
||||
import type { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer';
|
||||
|
||||
import { walk } from 'estree-walker';
|
||||
|
||||
// Optimizers
|
||||
import optimizeStyles from './styles.js';
|
||||
import optimizeDoctype from './doctype.js';
|
||||
import optimizeModuleScripts from './module-scripts.js';
|
||||
|
||||
interface VisitorCollection {
|
||||
enter: Map<string, VisitorFn[]>;
|
||||
|
@ -67,6 +70,7 @@ function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection)
|
|||
}
|
||||
|
||||
interface OptimizeOptions {
|
||||
compileOptions: CompileOptions;
|
||||
filename: string;
|
||||
fileID: string;
|
||||
}
|
||||
|
@ -76,7 +80,7 @@ export async function optimize(ast: Ast, opts: OptimizeOptions) {
|
|||
const cssVisitors = createVisitorCollection();
|
||||
const finalizers: Array<() => Promise<void>> = [];
|
||||
|
||||
const optimizers = [optimizeStyles(opts), optimizeDoctype(opts)];
|
||||
const optimizers = [optimizeStyles(opts), optimizeDoctype(opts), optimizeModuleScripts(opts)];
|
||||
|
||||
for (const optimizer of optimizers) {
|
||||
collectVisitors(optimizer, htmlVisitors, cssVisitors, finalizers);
|
||||
|
|
42
src/compiler/optimize/module-scripts.ts
Normal file
42
src/compiler/optimize/module-scripts.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import type { Optimizer } from '../../@types/optimizer';
|
||||
import type { CompileOptions } from '../../@types/compiler';
|
||||
|
||||
import path from 'path';
|
||||
import { getAttrValue, setAttrValue } from '../../ast.js';
|
||||
|
||||
export default function ({ compileOptions, filename }: { compileOptions: CompileOptions; filename: string; fileID: string }): Optimizer {
|
||||
const { astroConfig } = compileOptions;
|
||||
const { astroRoot } = astroConfig;
|
||||
const fileUrl = new URL(`file://${filename}`);
|
||||
|
||||
return {
|
||||
visitors: {
|
||||
html: {
|
||||
Element: {
|
||||
enter(node) {
|
||||
let name = node.name;
|
||||
if (name !== 'script') {
|
||||
return;
|
||||
}
|
||||
|
||||
let type = getAttrValue(node.attributes, 'type');
|
||||
if (type !== 'module') {
|
||||
return;
|
||||
}
|
||||
|
||||
let src = getAttrValue(node.attributes, 'src');
|
||||
if (!src || !src.startsWith('.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const srcUrl = new URL(src, fileUrl);
|
||||
const fromAstroRoot = path.posix.relative(astroRoot.pathname, srcUrl.pathname);
|
||||
const absoluteUrl = `/_astro/${fromAstroRoot}`;
|
||||
setAttrValue(node.attributes, 'src', absoluteUrl);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async finalize() {},
|
||||
};
|
||||
}
|
|
@ -17,6 +17,13 @@ export interface Fragment extends BaseNode {
|
|||
export interface Text extends BaseNode {
|
||||
type: 'Text';
|
||||
data: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface Attribute extends BaseNode {
|
||||
type: 'Attribute';
|
||||
name: string;
|
||||
value: Text[];
|
||||
}
|
||||
|
||||
export interface MustacheTag extends BaseNode {
|
||||
|
|
Loading…
Reference in a new issue