@mega-apps/vue-addon-loader
Vue 扩展加载器,适用于动态加载 Vue 组件。
Vue Addon Loader is a small library that allows you to load Vue components from a module dynamically. It's only dependent on the vue runtime. no need to install any other dependencies. No node.js or webpack dependencies.
1. 主要特性
- 完全支持 Vue2 的组件
- 可支持IE11版本
- 仅需要Vue运行时,无需其他依赖
- 提供 esm and umd 包
- 支持 JSX 语法
- 支持 嵌入ES6模块写法
- 支持自定义 CSS、HTML、脚本支持
- 支持 SFC 自定义模块
- 支持编译错误定位
- 支持远程组件,来源于网络、数据库、本地文件... 等
安装
# yarn 安装
yarn add @mega-apps/vue-addon-loader
# 或 pnpm 安装
pnpm add @mega-apps/vue-addon-loader
简单示例
2. 支持 JSX 语法
<script>
export default {
data() {
return {
type: 'jsx 代码',
msg: 'Hello Vue!'
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
{/* 注释 */ this.msg }
</div>
)
}
}
</script>
3. 支持的ECMAScript 特性
计算属性: computed-properties
3.1. 支持const foo = 'foo', bar = 'bar';
var obj = {
["x" + foo]: "heh",
["y" + bar]: "noo",
foo: "foo",
bar: "bar",
};
属性名表达式: property-name-expression
3.2. 支持const foo = 'foo', bar = 'bar';
var obj = {
[foo]: "heh",
[bar]: "noo",
foo: "foo",
bar: "bar",
};
属性简写: property-shorthand
3.3. 支持const foo = 'foo', bar = 'bar';
var obj = {
foo,
bar,
foo: "foo",
bar: "bar",
};
属性绑定: property-binding
3.4. 支持const foo = 'foo', bar = 'bar';
var obj = {
foo: "heh",
bar: "noo",
foo: "foo",
bar: "bar",
};
BigInt 大整数
3.5. 支持const expected = 4n / 2n;
模板字符串: template-string
3.6. 支持const foo = 'foo', bar = 'bar';
const info = `${foo} ${bar}`;
箭头函数: arrow-function
3.7. 支持const foo = 'foo', bar = 'bar';
const info = (foo, bar) => {
return foo + bar;
};
类: class
3.8. 支持class Foo {
constructor(foo, bar) {
this.foo = foo;
this.bar = bar;
}
}
类继承: class-extends
3.9. 支持class Foo extends Bar {
constructor(foo, bar) {
super(foo, bar);
this.foo = foo;
this.bar = bar;
}
}
类静态属性: class-static-properties
3.10. 支持class Foo {
static foo = 'foo';
static bar = 'bar';
}
类静态方法: class-static-methods
3.11. 支持class Foo {
static foo() {
return 'foo';
}
static bar() {
return 'bar';
}
}
类实例属性: class-instance-properties
3.12. 支持class Foo {
foo = 'foo';
bar = 'bar';
}
类实例方法: class-instance-methods
3.13. 支持class Foo {
foo() {
return 'foo';
}
bar() {
return 'bar';
}
}
类静态属性和方法: class-static-properties-and-methods
3.14. 支持class Foo {
static foo = 'foo';
static bar() {
return 'bar';
}
}
类实例属性和方法: class-instance-properties-and-methods
3.15. 支持class Foo {
foo = 'foo';
bar() {
return 'bar';
}
}
类静态属性和实例属性: class-static-properties-and-instance-properties
3.16. 支持class Foo {
static foo = 'foo';
foo = 'foo';
}
类静态属性和实例方法: class-static-properties-and-instance-methods
3.17. 支持class Foo {
static foo = 'foo';
foo() {
return 'foo';
}
}
异步生成器: async-generator
3.18. 支持async function* foo() {
yield 1;
yield 2;
yield 3;
}
类型断言: type-assertion
3.19. 支持const foo = 'foo';
const bar = 'bar';
const info = foo as string;
类型保护: type-guard
3.20. 支持if (foo instanceof Foo) {
foo.foo();
}
类型别名: type-alias
3.21. 支持type Foo = {
foo: string;
bar: string;
};
类私有属性和方法: private-property-and-methods
3.22. 支持class Foo {
#foo = 'foo';
#bar() {
return 'bar';
}
}
类属性修饰符: property-modifiers
3.23. 支持class Foo {
#foo = 'foo';
#bar() {
return 'bar';
}
get foo() {
return this.#foo;
}
set foo(value) {
this.#foo = value;
}
get bar() {
return this.#bar();
}
}
类静态块: class-static-block
3.24. 支持class Foo {
static foo = 'foo';
static bar = 'bar';
static #baz = 'baz';
static #qux() {
return 'qux';
}
}
类实例块: class-instance-block
3.25. 支持class Foo {
foo = 'foo';
bar = 'bar';
#baz = 'baz';
#qux() {
return 'qux';
}
}
动态导入: dynamic-import
3.26. 支持import('./foo.js').then(foo => {
foo.foo();
});
模块化导入: module
3.27. 支持import { foo } from './foo.js';
模块化默认导入: module-with-default-export
3.28. 支持import foo from './foo.js';
导出命名空间: export-namespace
3.29. 支持export * as foo from './foo.js';
导出类: export-class
3.30. 支持export class Foo {
constructor(foo, bar) {
this.foo = foo;
this.bar = bar;
}
}
函数sent特性: function-sent
3.31. 支持function foo(x = (y = z)) {
return x;
}
function *adder(total=0) {
let increment=1;
do {
switch (request = function.sent){
case undefined: break;
case "done": return total;
default: increment = Number(request);
}
yield total += increment;
} while (true)
}
逻辑赋值: logical-assignment-operators
3.32. 支持let b = 0;
let a = b ||= 1;
模块字符串: module-strings
3.33. 支持import { "😄" as smile } from "emojis";
空选链: nullish-coalescing-operator
3.34. 支持let foo = bar ?? 'foo';
空赋值: nullish-assignment-operator
3.35. 支持let foo = bar ??= 'foo';
对象rest spread: object-rest-spread
3.36. 支持let { foo, ...bar } = { foo: 'foo', bar: 'bar' };
数组rest spread: array-rest-spread
3.37. 支持let [foo, ...bar] = ['foo', 'bar'];
可选捕获绑定: optional-catch-binding
3.38. 支持try{
console.log(1);
}finally{
console.log('finally');
}
可选参数: optional-parameters
3.39. 支持function foo(x = 1) {
return x;
}
可选参数和可选属性: optional-chaining
3.40. 支持const foo = {
bar: {
baz: 'baz'
}
};
const baz = foo?.bar?.baz;
私有In: private-in
3.41. 支持class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
顶级 await: top-level-await
3.42. 支持async function foo() {
await bar();
}
// 依赖回滚
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}
4. 支持ECMAScript 提案
异步do表达式: async-do-expressions
4.1. 支持async
do {
await fetch('http://www.bing.com').json()
}
decimal
4.2. 支持let budget = 1_000_000_000_000;
console.log(budget === 10 ** 12); // true
let nibbles = 0b1010_0001_1000_0101;
console.log(!!(nibbles & (1 << 7))); // true
// Messages are sent as 24 bit values, but should be
// treated as 3 distinct bytes:
let message = 0xa0_b0_c0;
// What's the value of the upper most byte? It's A0, or 160.
// We can confirm that:
let a = (message >> 16) & 0xff;
console.log(a.toString(16), a); // a0, 160
// What's the value of the middle byte? It's B0, or 176.
// Let's just make sure...
let b = (message >> 8) & 0xff;
console.log(b.toString(16), b); // b0, 176
// What's the value of the lower most byte? It's C0, or 192.
// Again, let's prove that:
let c = message & 0xff;
console.log(c.toString(16), b); // c0, 192
装饰器:decorators
4.3. 支持function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(\`starting $\{ name \} with arguments $\{ args.join(", ") \} \`);
const ret = value.call(this, ...args);
console.log(\`ending $\{ name \}\`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
do表达式: do-expressions
4.4. 支持let x = do {
let y = 1;
y + 1;
};
导出默认值: export-default-from
4.5. 支持export default from './foo.js';
导出from: export-from
4.6. 支持export v from 'vue';
export default {
data() {
return {
type: 'exportDefaultFrom export from 语法',
msg: 'https://github.com/tc39/ecmascript-export-default-from',
isProposal: true
}
},
${commonCode}
}
4.7. 支持函数绑定: function-bind
const box = {
weight: 2,
getWeight() {
return this.weight;
},
};
const { getWeight } = box;
console.log(box.getWeight()); // prints '2'
const bigBox = { weight: 10 };
console.log(bigBox::getWeight()); // prints '10'
// Can be chained:
function add(val) {
return this + val;
}
console.log(bigBox::getWeight()::add(5)); // prints '15'
导入断言: import-assert
4.8. 支持import Vue from "vue" assert { type: "js" };
局部模块化: module-declaration
4.9. 支持 let m = module { export let y = 1; };
局部应用: partial-application
4.10. 支持 function add(x, y) { return x + y; }
const addOne = add(1, ?); // apply from the left
addOne(2); // 3
const addTen = add(?, 10); // apply from the right
addTen(2); // 12
流水线操作符: pipeline-operator
4.11. 支持let result = "hello"
|> console.log;
4.12. 支持 Record 和 元组类型
let x = #{x: 1, y: 2};
let y = #[1, 2];
抛出异常表达式: throw-expressions
4.13. 支持class Product {
get id() { return this._id; }
set id(value) { this._id = value || throw new Error("Invalid value"); }
}
5. 代码示例
// TODO: 要支持TailwindCss 的at rule
const commonCode = `
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> { !!this?.isProposal ? 'ECMAScript 提案' : 'ECMAScript 特性' } 【{this.type}】</h1>
<a href={/* 注释 */ this.msg } class="bg-blue-500"> { this.msg } </a>
</div>
)
}
`
export default {
base: {
"demo": `
<template>
<span class="bg-yellow-200 p-4">Hello from Vue {{ require('myCustomModel').vueVersion }} !</span>
</template>
`,
},
// 验证是否支持tailwindcss的at rule
// 试验场:https://play.tailwindcss.com/
// TODO:嵌入tailwind 的编译器,用来解析at rule,参照 https://play.tailwindcss.com/ 网站源码
tailwindcss: {
"at-rules": {
getContentData: () => {
return `
<template>
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold">验证TailwindCss at rule 是否可用</h1>
<button class="w-32 rounded-md bg-gray-300 customBtn">Button</button>
</div>
</template>
<style scoped>
.customBtn {
// 指令不可用
@apply bg-blue-700 text-white;
}
</style>
`
},
type: '.vue'
}
},
// 语法特性
syntax: {
"jsx": {
getContentData: () => {
return `
<script>
export default {
data() {
return {
type: 'jsx 代码',
msg: 'Hello Vue!'
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
{/* 注释 */ this.msg }
</div>
)
}
}
</script>
`
},
type: '.vue'
},
"__filename": {
getContentData: () => {
return `
<script>
export default {
data() {
return {
type: '__filename 注入',
msg: __filename
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
{/* 注释 */ this.msg }
</div>
)
}
}
</script>
`
},
type: '.vue'
},
"__dirname": {
getContentData: () => {
return `
<script>
export default {
data() {
return {
type: '__dirname 注入',
msg: __dirname
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
{/* 注释 */ this.msg }
</div>
)
}
}
</script>
`
},
type: '.vue'
},
//#region ECMASript 特性
"computed-properties": {
getContentData: () => {
return `
<script>
const foo = 'foo', bar = 'bar';
var obj = {
["x" + foo]: "heh",
["y" + bar]: "noo",
foo: "foo",
bar: "bar",
};
export default {
data() {
return {
type: 'computed-properties 计算属性',
msg: 'https://babeljs.io/docs/en/babel-plugin-transform-computed-properties'
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
<a href={/* 注释 */ this.msg } class="bg-blue-500"> { this.msg } </a>
</div>
)
}
}
</script>
`
},
type: '.vue'
},
"BigInt": {
getContentData: () => {
return `
<script>
const expected = 4n / 2n;
export default {
data() {
return {
type: 'BigInt 大整数',
msg: 'https://github.com/tc39/proposal-bigint'
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
<a href={/* 注释 */ this.msg } class="bg-blue-500"> { this.msg } </a>
</div>
)
}
}
</script>
`
},
type: '.vue'
},
"asyncGenerators": {
getContentData: () => {
return `
<script>
const fn1 = async function* () {};
export default {
data() {
return {
type: 'asyncGenerators 异步迭代器,生成器',
msg: 'https://github.com/tc39/proposal-async-iteration'
}
},
render(h) {
return (
<div class="flex flex-col items-center justify-center border border-gray-600 p-4 m-2">
<h1 class="text-xl font-bold"> 语法特性 【{this.type}】</h1>
<a href={/* 注释 */ this.msg } class="bg-blue-500"> { this.msg } </a>
</div>
)
}
}
</script>
`
},
type: '.vue'
},
"classProperties": {
getContentData: () => {
return `
<script>
class A { b = 1; }
export default {
data() {
return {
type: 'classProperties 类字段',
msg: 'https://github.com/tc39/proposal-class-public-fields'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"classPrivateProperties": {
getContentData: () => {
return `
<script>
class A { #b = 1; }
export default {
data() {
return {
type: 'classPrivateProperties 类私有字段',
msg: 'https://github.com/tc39/proposal-private-fields'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"classPrivateMethods": {
getContentData: () => {
return `
<script>
class A { #c() {} }
export default {
data() {
return {
type: 'classPrivateMethods 类私有方法',
msg: 'https://github.com/tc39/proposal-private-methods'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"classStaticBlock": {
getContentData: () => {
return `
<script>
class A { static {} }
export default {
data() {
return {
type: 'classStaticBlock 类静态块',
msg: 'https://github.com/tc39/proposal-class-static-block'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"dynamicImport": {
getContentData: () => {
return `
<script>
import('vue').then(() => {});
export default {
data() {
return {
type: 'dynamicImport 动态导入',
msg: 'https://github.com/tc39/proposal-dynamic-import'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"exportNamespaceFrom": {
getContentData: () => {
return `
<script>
export * as ns from "vue";
export default {
data() {
return {
type: 'exportNamespaceFrom 动态导入',
msg: 'https://github.com/leebyron/ecmascript-export-ns-from'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"functionSent": {
getContentData: () => {
return `
<script>
function *adder(total=0) {
let increment=1;
do {
switch (request = function.sent){
case undefined: break;
case "done": return total;
default: increment = Number(request);
}
yield total += increment;
} while (true)
}
export default {
data() {
return {
type: 'functionSent 动态导入',
msg: 'https://github.com/tc39/proposal-function.sent'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"logicalAssignment": {
getContentData: () => {
return `
<script>
let b = 0;
let a = b ||= 1;
export default {
data() {
return {
type: 'logicalAssignment 逻辑赋值',
msg: 'https://github.com/tc39/proposal-logical-assignment'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"moduleStringNames": {
getContentData: () => {
return `
<script>
import { "😄" as smile } from "emojis";
export default {
data() {
return {
type: 'moduleStringNames 模块字符串命名',
msg: 'https://github.com/tc39/ecma262/pull/2154'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"nullishCoalescingOperator": {
getContentData: () => {
return `
<script>
let a, b = 1;
let c = a ?? b;
export default {
data() {
return {
type: 'nullishCoalescingOperator 空值合并运算',
msg: 'https://github.com/babel/proposals/issues/14'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"objectRestSpread": {
getContentData: () => {
return `
<script>
const { a, ...rest } = { a: 1, b: 2, c: 3 };
export default {
data() {
return {
type: 'objectRestSpread 解构',
msg: 'https://github.com/tc39/proposal-object-rest-spread'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"optionalCatchBinding": {
getContentData: () => {
return `
<script>
// input
try{
console.log(1);
}finally{
console.log('finally');
}
export default {
data() {
return {
type: 'optionalCatchBinding 可选捕获绑定',
msg: 'https://github.com/babel/proposals/issues/7'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"optionalChaining": {
getContentData: () => {
return `
<script>
const obj = { name: 'Lee' };
const name = obj?.name;
export default {
data() {
return {
type: 'optionalChaining 可选链',
msg: 'https://github.com/tc39/proposal-optional-chaining'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"privateIn": {
getContentData: () => {
return `
<script>
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
export default {
data() {
return {
type: 'privateIn 私有字段In',
msg: 'https://github.com/tc39/proposal-private-fields-in-in'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"topLevelAwait": {
getContentData: () => {
return `
<script>
// 依赖回滚
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}
export default {
data() {
return {
type: 'topLevelAwait 顶层await,',
msg: 'https://github.com/tc39/proposal-top-level-await/'
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
//#endregion
//#region ECMAScript 提案
"asyncDoExpressions": {
getContentData: () => {
return `
<script>
async do { await fetch('http://www.bing.com').json() }
export default {
data() {
return {
type: 'asyncDoExpressions 顶层await,',
msg: 'https://github.com/tc39/proposal-async-do-expressions',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"decimal": {
getContentData: () => {
return `
<script>
let budget = 1_000_000_000_000;
console.log(budget === 10 ** 12); // true
let nibbles = 0b1010_0001_1000_0101;
console.log(!!(nibbles & (1 << 7))); // true
// Messages are sent as 24 bit values, but should be
// treated as 3 distinct bytes:
let message = 0xa0_b0_c0;
// What's the value of the upper most byte? It's A0, or 160.
// We can confirm that:
let a = (message >> 16) & 0xff;
console.log(a.toString(16), a); // a0, 160
// What's the value of the middle byte? It's B0, or 176.
// Let's just make sure...
let b = (message >> 8) & 0xff;
console.log(b.toString(16), b); // b0, 176
// What's the value of the lower most byte? It's C0, or 192.
// Again, let's prove that:
let c = message & 0xff;
console.log(c.toString(16), b); // c0, 192
export default {
data() {
return {
type: 'decimal 提案支持',
msg: 'https://github.com/tc39/proposal-decimal',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"decorators": {
getContentData: () => {
return `
<script>
function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(\`starting $\{ name \} with arguments $\{ args.join(", ") \} \`);
const ret = value.call(this, ...args);
console.log(\`ending $\{ name \}\`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
export default {
data() {
return {
type: 'decorators 装饰器',
msg: 'https://github.com/tc39/proposal-decorators',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"doExpressions": {
getContentData: () => {
return `
<script>
var a = do { if (true) { 'hi'; } };
export default {
data() {
return {
type: 'doExpressions do表达式',
msg: 'https://github.com/tc39/proposal-do-expressions',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"exportDefaultFrom": {
getContentData: () => {
return `
<script>
export v from 'vue';
export default {
data() {
return {
type: 'exportDefaultFrom export from 语法',
msg: 'https://github.com/tc39/ecmascript-export-default-from',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"functionBind": {
getContentData: () => {
return `
<script>
const box = {
weight: 2,
getWeight() {
return this.weight;
},
};
const { getWeight } = box;
console.log(box.getWeight()); // prints '2'
const bigBox = { weight: 10 };
console.log(bigBox::getWeight()); // prints '10'
// Can be chained:
function add(val) {
return this + val;
}
console.log(bigBox::getWeight()::add(5)); // prints '15'
export default {
data() {
return {
type: 'functionBind 函数绑定语法',
msg: 'https://github.com/zenparsing/es-function-bind',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"importAssertions": {
getContentData: () => {
return `
<script>
import Vue from "vue" assert { type: "js" };
export default {
data() {
return {
type: 'importAssertions 导入断言',
msg: 'https://github.com/tc39/proposal-import-assertions',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"moduleBlocks": {
getContentData: () => {
return `
<script>
let m = module { export let y = 1; };
export default {
data() {
return {
type: 'moduleBlocks 局部模块化',
msg: 'https://github.com/tc39/proposal-js-module-blocks',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"partialApplication": {
getContentData: () => {
return `
<script>
function add(x, y) { return x + y; }
const addOne = add(1, ?); // apply from the left
addOne(2); // 3
const addTen = add(?, 10); // apply from the right
addTen(2); // 12
export default {
data() {
return {
type: 'partialApplication 局部应用',
msg: 'https://github.com/babel/proposals/issues/32',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"pipelineOperator": {
getContentData: () => {
return `
<script>
let result = "hello"
|> console.log;
export default {
data() {
return {
type: 'pipelineOperator 流水线操作符',
msg: 'https://github.com/babel/proposals/issues/29',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"recordAndTuple": {
getContentData: () => {
return `
<script>
let x = #{x: 1, y: 2};
let y = #[1, 2];
export default {
data() {
return {
type: 'recordAndTuple 记录和元组',
msg: 'https://github.com/tc39/proposal-record-tuple',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
"throwExpressions": {
getContentData: () => {
return `
<script>
class Product {
get id() { return this._id; }
set id(value) { this._id = value || throw new Error("Invalid value"); }
}
export default {
data() {
return {
type: 'throwExpressions 抛出表达式',
msg: 'https://github.com/babel/proposals/issues/23',
isProposal: true
}
},
${commonCode}
}
</script>
`
},
type: '.vue'
},
//#endregion
},
// 复杂的组件
compex: {
"demo": {
getContentData: () => {
const text = `
<template>
<div class="flex flex-col items-center justify-center border border-blue-600 p-4 m-2">
<!--父组件局部指定传值-->
<section class="flex flex-col items-center justify-center py-2">
<h1 class="text-xl font-bold">远程组件 - 源码级别</h1>
<span>{{ !!$t ? $t('hello') : 'Hello' }} from Vue {{ require('myCustomModel').vueVersion }} !</span>
<span>{{ __filename }}</span>
</section>
<!--附加远程组件-->
<section class="flex flex-col items-center justify-center py-2">
<calendar-range :selection="selection" :events="calendarEvents"/>
<footer class="flex space-x-3 min-w-full">
<button class="w-32 flex items-center justify-center rounded-md bg-blue-700 text-white" @click="add">Add</button>
<button class="w-32 flex items-center justify-center rounded-md bg-gray-300 border border-gray-300" @click="remove">Remove</button>
<button class="myBtn w-32 flex items-center justify-center rounded-md border border-gray-300" @click="fetch">Fetch</button>
<button class="myBtn w-32 flex items-center justify-center rounded-md bg-pink-500 border border-gray-300" @click="log">Log</button>
</footer>
</section>
<!-- 验证是否可以使用全局组件 -->
<section class="flex flex-col items-center justify-center py-2">
<header class="flex flex-col items-center justify-center">
<span class="text-xl font-bold"> 验证是否可以使用全局组件</span>
</header>
<body class="flex flex-col items-center justify-center">
<lazy-component-text :data="'Lazy loading the global compoent'" />
<component-text :data="'Loading the global compoent'" class="bg-gray-400 rounded"/>
</body>
</section>
<!-- 验证是否可以使用父组件传递过来的局部组件 -->
<section class="flex flex-col items-center justify-center py-2">
<header class="flex flex-col items-center justify-center">
<span class="text-xl font-bold"> 验证是否可以使用父组件传递过来的局部组件 </span>
</header>
<body class="flex flex-col items-center justify-center">
<CustomComponent name="909090L"/>
</body>
</section>
</div>
</template>
<script>
// 验证:加载互联网远端依赖组件
import calendarRange from 'https://raw.githubusercontent.com/FranckFreiburger/vue-calendar-picker/v1.2.1/src/calendarRange.vue'
import CustomComponent from 'customComponent';
export default {
// components: true,
components: {
calendarRange,
CustomComponent,
},
data() {
return {
selection: { start: Date.now(), end: Date.now() },
calendarEvents: [],
bgcolor: 'red'
}
},
methods: {
add: function() {
this.calendarEvents.push({
color: '#'+Math.floor(Math.random()*16777215).toString(16),
start: this.selection.start,
end: this.selection.end
});
},
remove() {
this.calendarEvents.pop();
},
fetch: async () => {
const url = "https://raw.githubusercontent.com/FranckFreiburger/vue-calendar-picker/v1.2.1/src/calendarRange.vue";
const res = await fetch(url);
// 无法解析\`\`
console.log(res);
alert('fetch 请求,状态码: ' + res.status);
},
log() {
console.log('vm=', this);
}
}
}
</script>
<style scoped>
.myBtn {
// 验证:关于css指令的处理还不支持
@apply bg-blue-700;
}
</style>
<i18n>
{
"en": {
"hello": "hello world!"
},
"ja": {
"hello": "こんにちは、世界!"
},
"zh": {
"hello": "你好,世界!"
}
}
</i18n>
`;
return text;
},
type: '.vue'
}
},
}
<template>
<div class="flex flex-col justify-center items-center">
<div class="flex flex-col py-4 px-4 min-w-min">
<h3 v-if="remoteComponentList.length < 1">Loading remote vue component file contents ...</h3>
<span class="bg-yellow-600 text-center border border-dotted border-yellow-300 p-4 m-2">待验证支持的语法仍有: {{ remoteComponentList.filter(({component}) => !component).length }} 个</span>
<div class="flex flex-col justify-center items-center text-center border border-dotted border-yellow-300 p-4 m-2" v-for="({component, description }, index) of remoteComponentList" :key="index">
<span :class=" component ? 'bg-green-100': 'bg-red-700 text-yellow-200' ">{{ description }}</span>
<component
v-if="component"
:is="component"
></component>
</div>
</div>
<div class="flex justify-center items-center text-center py-1 m-10px h-1/2">
<div class="flex">
<div class="flex-none w-48 relative">
<img
src="https://www.tailwindcss.cn/_next/static/media/kids-jumper.47a06f045002a3e6ba595351a36a46eb.jpg"
alt
class="absolute inset-0 w-full h-full object-cover"
/>
</div>
<form class="flex-auto p-6">
<div class="flex flex-wrap">
<h1 class="flex-auto text-xl font-semibold">Classic Utility Jacket</h1>
<div class="text-xl font-semibold text-gray-500">$110.00</div>
<div class="w-full flex-none text-sm font-medium text-gray-500 mt-2">In stock</div>
</div>
<div class="flex items-baseline mt-4 mb-6">
<div class="space-x-2 flex">
<label>
<input
class="w-9 h-9 flex items-center justify-center bg-gray-100 rounded-lg"
name="size"
type="radio"
value="xs"
checked
/>
XS
</label>
<label>
<input
class="w-9 h-9 flex items-center justify-center"
name="size"
type="radio"
value="s"
/>
S
</label>
<label>
<input
class="w-9 h-9 flex items-center justify-center"
name="size"
type="radio"
value="m"
/>
M
</label>
<label>
<input
class="w-9 h-9 flex items-center justify-center"
name="size"
type="radio"
value="l"
/>
L
</label>
<label>
<input
class="w-9 h-9 flex items-center justify-center"
name="size"
type="radio"
value="xl"
/>
XL
</label>
</div>
<div class="ml-auto text-sm text-gray-500 underline">Size Guide</div>
</div>
<div class="flex space-x-3 mb-4 text-sm font-medium">
<div class="flex-auto flex space-x-3">
<button
class="w-1/2 flex items-center justify-center rounded-md bg-black text-white"
type="submit"
>Buy now</button>
<button
class="w-1/2 flex items-center justify-center rounded-md border border-gray-300"
type="button"
>Add to bag</button>
</div>
<button
class="flex-none flex items-center justify-center w-9 h-9 rounded-md text-gray-400 border border-gray-300"
type="button"
aria-label="like"
>
<svg width="20" height="20" fill="currentColor">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
/>
</svg>
</button>
</div>
<p class="text-sm text-gray-500">Free shipping on all continental US orders.</p>
</form>
</div>
<button
class="bg-blue mt-4 hover:bg-blue-dark text-white font-bold py-2 px-4 rounded"
@click="$router.push('/about')"
>About</button>
</div>
</div>
</template>
<script lang="js">
import Vue from "vue";
import * as emojis from "emojis-list";
import _ from "lodash";
import dateFnsLocalZHCN from "date-fns/locale/zh_cn";
import extendVueSfcLoader from "./vue-loader";
import componentRemoteCfg from "./data-remote-components";
export default {
data() {
return {
remoteComponentList: []
}
},
mounted() {
this.fetchData();
},
methods: {
fetchData() {
console.log('----->',extendVueSfcLoader);
extendVueSfcLoader.loadModule(() => {
const { deps } = extendVueSfcLoader;
const { loadModule,vueVersion } = deps[Vue.version];
const options = {
moduleCache: {
// 系统级别模块
vue: Vue,
emojis,
// 自定义模块
myCustomModel: {
vueVersion: vueVersion,
},
// 自定义组件模块
customComponent: {
name: "CustomComponentDemo",
render(h) {
return h("div",{
class: "bg-red-500 text-white p-2 roundedb"
},[
h("h1",{},[ `${this.name} - ${this.title}`] ),
h("p",{},this.message),
h(
'button',
{
class: 'w-32 flex items-center justify-center rounded-md bg-gray-300 border border-gray-300',
on: {
click: () => {
this.toggleMood();
}
}
},
this.mood ? 'On' : 'Off'
)
]);
},
props: {
name: {
type: String,
default: '',
},
},
data() {
return {
mood: true,
title: 'Vue2 Title',
message: 'Vue2 Message',
}
},
methods: {
toggleMood() {
this.mood = !this.mood;
this.mood ? this.title = "Clicked" : this.title = "Not Clicked";
}
}
},
// 加载模块的几种方式
'date-fns/locale/en/index.js': {}, // handle require('date-fns/locale/' + this.locale.toLowerCase() + '/index.js');
'date-fns/locale/zh-tw/index.js': require('date-fns/locale/zh_tw/index.js'),
'date-fns/locale/zh-cn/index.js': dateFnsLocalZHCN, // handle require('date-fns/locale/' + this.locale.toLowerCase() + '/index.js');
'date-fns/locale/zh/index.js': dateFnsLocalZHCN, // handle require('date-fns/locale/' + this.locale.toLowerCase() + '/index.js');
},
pathResolve({ refPath,relPath }) {
if (relPath === 'date-fns')
return 'https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js';
if (relPath === '.') // self
return refPath;
// relPath is a module name ?
if (relPath[0] !== '.' && relPath[0] !== '/')
return relPath;
return String(new URL(relPath,refPath === undefined ? window.location : refPath));
},
getFile: async (url) => {
const pathName = new URL(url).pathname;
console.log('%c%s','color: #aa00ff',`pathName = ${pathName}`);
if (pathName.startsWith('/get-remote/')) {
const actions = [
(e) => e.replace(/^\/get-remote\//, ''),
pathName.endsWith('.vue') ? (e) => e.slice(0, -4) : null,
(e) => e.split('/')
];
const componentKeys = actions.reduce((acc, cur) => {
if (cur) {
return cur(acc);
}
return acc;
}, pathName);
const component = _.get(componentRemoteCfg,componentKeys);
if (component) {
console.log(`${pathName}\n`,component)
return Promise.resolve(component);
}
} else {
return fetch(url).then(res => res.text());
}
return Promise.reject(`${pathName} not found`);
},
addStyle(textContent) {
if (textContent?.length > 0) {
// console.log('%c%s', 'color: #e50000', `addStyle(textContent): textContent`, textContent);
const style = Object.assign(document.createElement('style'),{ textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style,ref);
}
},
}
const { base, tailwindcss, syntax, compex } = componentRemoteCfg;
for (const url of [
...Object.keys(base).map(e => `base/${e}`),
...Object.keys(tailwindcss).map(e => `tailwindcss/${e}`),
...Object.keys(syntax).map(e => `syntax/${e}`),
...Object.keys(compex).map(e => `compex/${e}`),
].map(e => `/get-remote/${e}`)) {
console.log('%c%s','color: #733d00',`url => ${url}`)
loadModule(url,options).then((component) => {
console.log('%c%s','color: #ff0000',`已构造完成远程组件(${url}) =`,component);
this.remoteComponentList.push({
component,
description: url,
});
}).catch(err => {
console.error('%c%s','color: #aa00ff',`构造远程组件失败(${url}) =`,err);
this.remoteComponentList.push({
component: null,
description: `${url} 错误: ${err}`,
});
});
}
})
},
},
};
</script>