svelte/register compatible with .mjs files including svelte components

pull/5242/head
Amir Nouri 5 years ago
parent f4f16da455
commit d4bb5f4a7a

@ -1,20 +1,32 @@
const fs = require('fs');
const path = require('path');
const { compile } = require('./compiler.js');
const fs = require("fs");
const path = require("path");
const { compile } = require("./compiler.js");
const extensions = ['.svelte', '.html'];
const extensions = [".svelte", ".html"];
let es6Compiler;
let compileOptions = {};
function compileES6(code) {
try {
return es6Compiler(code);
} catch (error) {
console.log(`error compiling esm file ${error}`);
}
}
function capitalise(name) {
return name[0].toUpperCase() + name.slice(1);
}
function register(options = {}) {
function register(options = {}, es6_compiler) {
if (options.extensions) {
extensions.forEach(deregisterExtension);
options.extensions.forEach(registerExtension);
}
// To compile mjs extensions
if (es6_compiler) es6Compiler = es6_compiler;
compileOptions = Object.assign({}, options);
delete compileOptions.extensions;
}
@ -24,33 +36,51 @@ function deregisterExtension(extension) {
}
function registerExtension(extension) {
require.extensions[extension] = function(module, filename) {
const name = path.parse(filename).name
.replace(/^\d/, '_$&')
.replace(/[^a-zA-Z0-9_$]/g, '');
require.extensions[extension] = function (module, filename) {
const name = path
.parse(filename)
.name.replace(/^\d/, "_$&")
.replace(/[^a-zA-Z0-9_$]/g, "");
const options = Object.assign({}, compileOptions, {
filename,
name: capitalise(name),
generate: 'ssr',
format: 'cjs'
generate: compileOptions.generate || "ssr",
format: "cjs",
});
const { js, warnings } = compile(fs.readFileSync(filename, 'utf-8'), options);
const { js, css, ast, warnings, vars, stats } = compile(
fs.readFileSync(filename, "utf-8"),
options
);
if (options.dev) {
warnings.forEach(warning => {
warnings.forEach((warning) => {
console.warn(`\nSvelte Warning in ${warning.filename}:`);
console.warn(warning.message);
console.warn(warning.frame);
})
});
}
return module._compile(js.code, filename);
let compiled = module._compile(js.code, filename);
return compiled;
};
}
registerExtension('.svelte');
registerExtension('.html');
registerExtension(".svelte");
registerExtension(".html");
// To handle mixture of CJS and ESM modules .
require.extensions[".mjs"] = function (module, filename) {
try {
if (es6Compiler == null) {
throw `error es6Compiler is not set`;
}
let code = compileES6(fs.readFileSync(filename, "utf-8")).code;
return module._compile(code, filename);
} catch (error) {
console.log(`error ${error}`);
}
};
module.exports = register;

@ -1,11 +1,18 @@
import Block from './Block';
import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
import { x } from 'code-red';
import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression } from 'estree';
import flatten_reference from '../utils/flatten_reference';
import { reserved_keywords } from '../utils/reserved_keywords';
import Block from "./Block";
import { CompileOptions, Var } from "../../interfaces";
import Component from "../Component";
import FragmentWrapper from "./wrappers/Fragment";
import { x } from "code-red";
import {
Node,
Identifier,
MemberExpression,
Literal,
Expression,
BinaryExpression,
} from "estree";
import flatten_reference from "../utils/flatten_reference";
import { reserved_keywords } from "../utils/reserved_keywords";
interface ContextMember {
name: string;
@ -32,7 +39,15 @@ export default class Renderer {
blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: Map<string, { binding_group: (to_reference?: boolean) => Node; is_context: boolean; contexts: string[]; index: number }> = new Map();
binding_groups: Map<
string,
{
binding_group: (to_reference?: boolean) => Node;
is_context: boolean;
contexts: string[];
index: number;
}
> = new Map();
block: Block;
fragment: FragmentWrapper;
@ -45,33 +60,37 @@ export default class Renderer {
this.options = options;
this.locate = component.locate; // TODO messy
this.file_var = options.dev && this.component.get_unique_name('file');
this.file_var = options.dev && this.component.get_unique_name("file");
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name));
component.vars
.filter((v) => !v.hoistable || (v.export_name && !v.module))
.forEach((v) => this.add_to_context(v.name));
// ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`));
component.vars
.filter((v) => v.subscribable)
.forEach((v) => this.add_to_context(`$${v.name}`));
reserved_keywords.forEach(keyword => {
reserved_keywords.forEach((keyword) => {
if (component.var_lookup.has(keyword)) {
this.add_to_context(keyword);
}
});
if (component.slots.size > 0) {
this.add_to_context('$$scope');
this.add_to_context('$$slots');
this.add_to_context("$$scope");
this.add_to_context("$$slots");
}
if (this.binding_groups.size > 0) {
this.add_to_context('$$binding_groups');
this.add_to_context("$$binding_groups");
}
// main block
this.block = new Block({
renderer: this,
name: null,
type: 'component',
type: "component",
key: null,
bindings: new Map(),
@ -91,7 +110,7 @@ export default class Renderer {
);
// TODO messy
this.blocks.forEach(block => {
this.blocks.forEach((block) => {
if (block instanceof Block) {
block.assign_variable_names();
}
@ -103,7 +122,7 @@ export default class Renderer {
this.context_overflow = this.context.length > 31;
this.context.forEach(member => {
this.context.forEach((member) => {
const { variable } = member;
if (variable) {
member.priority += 2;
@ -124,8 +143,12 @@ export default class Renderer {
}
});
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number)));
this.context.forEach((member, i) => member.index.value = i);
this.context.sort(
(a, b) =>
b.priority - a.priority ||
(a.index.value as number) - (b.index.value as number)
);
this.context.forEach((member, i) => (member.index.value = i));
let i = this.context.length;
while (i--) {
@ -143,11 +166,11 @@ export default class Renderer {
if (!this.context_lookup.has(name)) {
const member: ContextMember = {
name,
index: { type: 'Literal', value: this.context.length }, // index is updated later, but set here to preserve order within groups
index: { type: "Literal", value: this.context.length }, // index is updated later, but set here to preserve order within groups
is_contextual: false,
is_non_contextual: false, // shadowed vars could be contextual and non-contextual
variable: null,
priority: 0
priority: 0,
};
this.context_lookup.set(name, member);
@ -171,23 +194,27 @@ export default class Renderer {
const variable = this.component.var_lookup.get(name);
const member = this.context_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
if (
variable &&
variable.subscribable &&
(variable.reassigned || variable.export_name)
) {
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${
value || name
}))`;
}
if (name[0] === '$' && name[1] !== '$') {
if (name[0] === "$" && name[1] !== "$") {
return x`${name.slice(1)}.set(${value || name})`;
}
if (
variable && (
variable.module || (
!variable.referenced &&
variable &&
(variable.module ||
(!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
)
)
!name.startsWith("$$")))
) {
return value || name;
}
@ -199,32 +226,32 @@ export default class Renderer {
// if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]);
deps.forEach(name => {
const reactive_declarations = this.component.reactive_declarations.filter(x =>
x.assignees.has(name)
deps.forEach((name) => {
const reactive_declarations = this.component.reactive_declarations.filter(
(x) => x.assignees.has(name)
);
reactive_declarations.forEach(declaration => {
declaration.dependencies.forEach(name => {
reactive_declarations.forEach((declaration) => {
declaration.dependencies.forEach((name) => {
deps.add(name);
});
});
});
// TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => this.context_lookup.has(n));
const filtered = Array.from(deps).filter((n) => this.context_lookup.has(n));
if (!filtered.length) return null;
return filtered
.map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.map((n) => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
}
dirty(names, is_reactive_declaration = false): Expression {
const renderer = this;
const dirty = (is_reactive_declaration
? x`$$self.$$.dirty`
: x`#dirty`) as Identifier | MemberExpression;
const dirty = (is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`) as
| Identifier
| MemberExpression;
const get_bitmask = () => {
const bitmask: BitMasks = [];
@ -239,7 +266,7 @@ export default class Renderer {
const value = member.index.value as number;
const i = (value / 31) | 0;
const n = 1 << (value % 31);
const n = 1 << value % 31;
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
@ -255,30 +282,34 @@ export default class Renderer {
// the expression lazily. TODO would be better if
// context was determined before rendering, so that
// this indirection was unnecessary
type: 'ParenthesizedExpression',
type: "ParenthesizedExpression",
get expression() {
const bitmask = get_bitmask();
if (!bitmask.length) {
return x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression;
return x`${dirty} & /*${names.join(", ")}*/ 0` as BinaryExpression;
}
if (renderer.context_overflow) {
return bitmask
.map((b, i) => ({ b, i }))
.filter(({ b }) => b)
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
.map(
({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(", ")}*/ ${b.n}`
)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
}
return x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression;
}
return x`${dirty} & /*${names.join(", ")}*/ ${
bitmask[0].n
}` as BinaryExpression;
},
} as any;
}
reference(node: string | Identifier | MemberExpression) {
if (typeof node === 'string') {
node = { type: 'Identifier', name: node };
if (typeof node === "string") {
node = { type: "Identifier", name: node };
}
const { name, nodes } = flatten_reference(node);

@ -1,6 +1,6 @@
import { set_current_component, current_component } from './lifecycle';
import { run_all, blank_object } from './utils';
import { boolean_attributes } from '../../compiler/compile/render_ssr/handlers/shared/boolean_attributes';
import { set_current_component, current_component } from "./lifecycle";
import { run_all, blank_object } from "./utils";
import { boolean_attributes } from "../../compiler/compile/render_ssr/handlers/shared/boolean_attributes";
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
@ -12,12 +12,12 @@ export function spread(args, classes_to_add) {
if (attributes.class == null) {
attributes.class = classes_to_add;
} else {
attributes.class += ' ' + classes_to_add;
attributes.class += " " + classes_to_add;
}
}
let str = '';
let str = "";
Object.keys(attributes).forEach(name => {
Object.keys(attributes).forEach((name) => {
if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name];
@ -25,7 +25,9 @@ export function spread(args, classes_to_add) {
else if (boolean_attributes.has(name.toLowerCase())) {
if (value) str += " " + name;
} else if (value != null) {
str += ` ${name}="${String(value).replace(/"/g, '&#34;').replace(/'/g, '&#39;')}"`;
str += ` ${name}="${String(value)
.replace(/"/g, "&#34;")
.replace(/'/g, "&#39;")}"`;
}
});
@ -33,19 +35,19 @@ export function spread(args, classes_to_add) {
}
export const escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
'"': "&quot;",
"'": "&#39;",
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
};
export function escape(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]);
return String(html).replace(/["'&<>]/g, (match) => escaped[match]);
}
export function each(items, fn) {
let str = '';
let str = "";
for (let i = 0; i < items.length; i += 1) {
str += fn(items[i], i);
}
@ -53,22 +55,25 @@ export function each(items, fn) {
}
export const missing_component = {
$$render: () => ''
$$render: () => "",
};
export function validate_component(component, name) {
if (component.default) component = component.default;
if (!component || !component.$$render) {
if (name === 'svelte:component') name += ' this={...}';
throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`);
if (name === "svelte:component") name += " this={...}";
throw new Error(
`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`
);
}
return component;
}
export function debug(file, line, column, values) {
console.log(`{@debug} ${file ? file + ' ' : ''}(${line}:${column})`); // eslint-disable-line no-console
console.log(`{@debug} ${file ? file + " " : ""}(${line}:${column})`); // eslint-disable-line no-console
console.log(values); // eslint-disable-line no-console
return '';
return "";
}
let on_destroy;
@ -85,7 +90,7 @@ export function create_ssr_component(fn) {
on_mount: [],
before_update: [],
after_update: [],
callbacks: blank_object()
callbacks: blank_object(),
};
set_current_component({ $$ });
@ -107,7 +112,7 @@ export function create_ssr_component(fn) {
map: null;
code: string;
}>;
} = { title: '', head: '', css: new Set() };
} = { title: "", head: "", css: new Set() };
const html = $$render(result, props, {}, options);
@ -116,22 +121,32 @@ export function create_ssr_component(fn) {
return {
html,
css: {
code: Array.from(result.css).map(css => css.code).join('\n'),
map: null // TODO
code: Array.from(result.css)
.map((css) => css.code)
.join("\n"),
map: null, // TODO
},
head: result.title + result.head
head: result.title + result.head,
};
},
$$render
$$render,
};
}
export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return '';
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
if (value == null || (boolean && !value)) return "";
return ` ${name}${
value === true
? ""
: `=${
typeof value === "string"
? JSON.stringify(escape(value))
: `"${value}"`
}`
}`;
}
export function add_classes(classes) {
return classes ? ` class="${classes}"` : ``;
}
}

Loading…
Cancel
Save