mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
feat: import compiler from marko-js/x
Co-authored-by: Michael Rawlings <mirawlings@ebay.com> Co-authored-by: Dylan Piercey <dpiercey@ebay.com> Co-authored-by: Andrew Gliga <agliga@ebay.com>
This commit is contained in:
parent
ea6736d085
commit
02670c8693
1
.browserslistrc
Normal file
1
.browserslistrc
Normal file
@ -0,0 +1 @@
|
||||
extends @ebay/browserslist-config
|
||||
6
.commitlintrc.json
Normal file
6
.commitlintrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"@commitlint/config-lerna-scopes",
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
}
|
||||
@ -1,18 +1,13 @@
|
||||
# minified
|
||||
*.min.js
|
||||
|
||||
# test
|
||||
*.html.js
|
||||
*.marko.js
|
||||
*actual*
|
||||
*expected*
|
||||
input.js
|
||||
|
||||
# generated
|
||||
*dist*
|
||||
*generated*
|
||||
*.marko.js
|
||||
*.marko.*.js
|
||||
*.html.js
|
||||
.cache/
|
||||
.nyc_output/
|
||||
node_modules/
|
||||
coverage/
|
||||
*dist/
|
||||
*generated/
|
||||
~vdom.skip
|
||||
coverage
|
||||
|
||||
node_modules
|
||||
**/test/**/input.js
|
||||
@ -1,11 +1,19 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"document": true,
|
||||
"ShadowRoot": true
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
||||
|
||||
133
.gitignore
vendored
133
.gitignore
vendored
@ -1,22 +1,117 @@
|
||||
/work
|
||||
/build
|
||||
.idea/
|
||||
npm-debug.log
|
||||
node_modules
|
||||
*.sublime-workspace
|
||||
*.orig
|
||||
.DS_Store
|
||||
.vscode
|
||||
coverage
|
||||
.nvmrc
|
||||
~*
|
||||
/.cache
|
||||
### Project ###
|
||||
|
||||
*.marko.js
|
||||
*.marko.xml.js
|
||||
/test/generated/
|
||||
*actual.*
|
||||
dist
|
||||
|
||||
### Node ###
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn
|
||||
.yarn-integrity
|
||||
yarn.lock
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
### Git ###
|
||||
|
||||
# Created by git for backups. To disable backups in Git:
|
||||
# $ git config --global mergetool.keepBackup false
|
||||
*.orig
|
||||
|
||||
# Created by git when using merge tools for conflicts
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
|
||||
### OS ###
|
||||
|
||||
# Linux
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
Icon
|
||||
|
||||
# Windows
|
||||
*[Tt]humbs*.db*
|
||||
[Dd]esktop.ini
|
||||
*.stackdump
|
||||
*.lnk
|
||||
|
||||
### EDITOR ###
|
||||
|
||||
# VisualStudioCode
|
||||
.vscode
|
||||
.history
|
||||
|
||||
# JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
modules.xml
|
||||
.idea
|
||||
|
||||
# SublimeText
|
||||
*.cache
|
||||
*.sublime-*
|
||||
Package Control.*
|
||||
oscrypto-ca-bundle.crt
|
||||
|
||||
# TextMate
|
||||
*.tmproj
|
||||
*.tmproject
|
||||
tmtags
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
/**/dist/
|
||||
/**/test-dist/
|
||||
/**/test-generated/
|
||||
.vs/
|
||||
*.vim
|
||||
.netrwhist
|
||||
|
||||
18
.npmignore
18
.npmignore
@ -1,18 +0,0 @@
|
||||
/test
|
||||
/work
|
||||
/build
|
||||
/.idea/
|
||||
/npm-debug.log
|
||||
/node_modules
|
||||
/*.sublime-workspace
|
||||
*.orig
|
||||
.DS_Store
|
||||
.vscode
|
||||
coverage
|
||||
.nvmrc
|
||||
/benchmark
|
||||
/.cache
|
||||
.nyc_output
|
||||
~*
|
||||
yarn.lock
|
||||
*.afdesign
|
||||
16
.nycrc
Normal file
16
.nycrc
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"all": true,
|
||||
"cache": true,
|
||||
"include": [
|
||||
"packages/*/src/**/*.js"
|
||||
],
|
||||
"instrument": false,
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
],
|
||||
"require": [
|
||||
"@babel/register"
|
||||
],
|
||||
"sourceMap": false
|
||||
}
|
||||
@ -7,17 +7,20 @@
|
||||
input.*
|
||||
|
||||
# generated
|
||||
/**/dist/
|
||||
/**/test-dist/
|
||||
/**/test-generated/
|
||||
dist/
|
||||
*.marko.js
|
||||
*.html.js
|
||||
*.xml.js
|
||||
*.generated.js
|
||||
.nyc_output
|
||||
.cache/
|
||||
coverage
|
||||
~*
|
||||
|
||||
# controlled by npm's formatter
|
||||
package-lock.json
|
||||
package.json
|
||||
|
||||
# controlled by lerna
|
||||
CHANGELOG.md
|
||||
lerna.json
|
||||
@ -1,10 +1,8 @@
|
||||
sudo: false
|
||||
node_js:
|
||||
- "8"
|
||||
- "10"
|
||||
- "12"
|
||||
language: node_js
|
||||
before_install: "npm i -g npm@latest"
|
||||
install: "npm ci"
|
||||
script: "npm run test-ci"
|
||||
after_success: "npm run codecov"
|
||||
script: "npm run ci:test"
|
||||
after_success: "npm run ci:codecov"
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2016 eBay Inc
|
||||
Copyright 2020 eBay Inc. and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
35
babel.config.js
Normal file
35
babel.config.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = api => ({
|
||||
retainLines: true,
|
||||
presets: [
|
||||
[
|
||||
"@babel/env",
|
||||
{
|
||||
loose: true,
|
||||
targets: {
|
||||
node: "8"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins: [
|
||||
"@babel/transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
test: "./packages/*/src/**/*",
|
||||
plugins: api.env("production")
|
||||
? [
|
||||
["babel-plugin-minprops", { matchPrefix: "___", context: "marko" }],
|
||||
"./scripts/babel-plugin-marko-debug"
|
||||
]
|
||||
: []
|
||||
}
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
plugins: ["babel-plugin-istanbul"]
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
1
benchmark/.gitignore
vendored
1
benchmark/.gitignore
vendored
@ -1 +0,0 @@
|
||||
static/
|
||||
@ -1,21 +0,0 @@
|
||||
var nodePath = require("path");
|
||||
|
||||
var Module = require("module").Module;
|
||||
var oldResolveFilename = Module._resolveFilename;
|
||||
|
||||
var rootDir = nodePath.join(__dirname, "../");
|
||||
|
||||
Module._resolveFilename = function(request, parent, isMain) {
|
||||
if (request.charAt(0) !== ".") {
|
||||
var firstSlash = request.indexOf("/");
|
||||
var targetPackageName =
|
||||
firstSlash === -1 ? request : request.substring(0, firstSlash);
|
||||
|
||||
if (targetPackageName === "marko") {
|
||||
request = request.substring("marko".length);
|
||||
request = rootDir + request;
|
||||
}
|
||||
}
|
||||
|
||||
return oldResolveFilename.call(this, request, parent, isMain);
|
||||
};
|
||||
2
benchmark/size/.gitignore
vendored
2
benchmark/size/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/build/
|
||||
/node_modules/
|
||||
@ -1,6 +0,0 @@
|
||||
import Inferno, { render } from 'inferno';
|
||||
import App from './components/App';
|
||||
|
||||
render(
|
||||
<App name='Frank' colors={['red', 'green', 'blue']}/>,
|
||||
document.body);
|
||||
@ -1,57 +0,0 @@
|
||||
'use strict';
|
||||
import Inferno from 'inferno';
|
||||
import Component from 'inferno-component';
|
||||
|
||||
function renderColor(color) {
|
||||
var style = {
|
||||
backgroundColor: color
|
||||
};
|
||||
|
||||
return <li className="color" style={style}>
|
||||
{color}
|
||||
</li>
|
||||
}
|
||||
|
||||
function renderColors(colors) {
|
||||
if (colors.length) {
|
||||
return (<ul>{colors.map(renderColor)}</ul>);
|
||||
} else {
|
||||
return <div>No colors!</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: props.name,
|
||||
colors: props.colors,
|
||||
clickCount: 0
|
||||
}
|
||||
|
||||
this.handleButtonClick = function() {
|
||||
this.setState({
|
||||
clickCount: this.state.clickCount + 1
|
||||
});
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
var colors = this.state.colors;
|
||||
var name = this.state.name;
|
||||
var clickCount = this.state.clickCount;
|
||||
var handleButtonClick = this.handleButtonClick;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello {name}!</h1>
|
||||
<div className="colors">
|
||||
{renderColors(colors)}
|
||||
</div>
|
||||
<button type="button" onClick={handleButtonClick}>
|
||||
You clicked the button {clickCount} time(s)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,32 +0,0 @@
|
||||
import browserifyPlugin from "rollup-plugin-browserify-transform";
|
||||
import nodeResolvePlugin from "rollup-plugin-node-resolve";
|
||||
import babelPlugin from "rollup-plugin-babel";
|
||||
import envify from "envify";
|
||||
import path from "path";
|
||||
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
|
||||
|
||||
export default {
|
||||
entry: path.join(__dirname, "client.jsx"),
|
||||
format: "iife",
|
||||
moduleName: "app",
|
||||
plugins: [
|
||||
babelPlugin({
|
||||
include: [],
|
||||
babelrc: false,
|
||||
presets: [["es2015", { loose: true, modules: false }], "stage-0"],
|
||||
plugins: ["inferno"]
|
||||
}),
|
||||
browserifyPlugin(envify),
|
||||
nodeResolvePlugin({
|
||||
jsnext: false, // Default: false
|
||||
main: true, // Default: true
|
||||
browser: true, // Default: false
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".jsx"]
|
||||
})
|
||||
],
|
||||
dest: path.join(__dirname, "../build/bundles/inferno.js")
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Marko</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Marko</h1>
|
||||
<script src="./build/bundles.min/marko.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,7 +0,0 @@
|
||||
var app = require("./components/app");
|
||||
app
|
||||
.renderSync({
|
||||
name: "Frank",
|
||||
colors: ["red", "green", "blue"]
|
||||
})
|
||||
.appendTo(document.body);
|
||||
@ -1,23 +0,0 @@
|
||||
class {
|
||||
onInput() {
|
||||
this.state = {
|
||||
clickCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
handleButtonClick() {
|
||||
this.state.clickCount++;
|
||||
}
|
||||
}
|
||||
|
||||
div
|
||||
h1 -- Hello ${input.name}!
|
||||
div.colors
|
||||
ul if(input.colors.length)
|
||||
for(color in input.colors)
|
||||
li.color style="background-color: ${color}"
|
||||
-- ${color}
|
||||
div else
|
||||
-- No colors!
|
||||
button type="button" onClick('handleButtonClick')
|
||||
-- You clicked the button ${state.clickCount} time(s)
|
||||
@ -1,3 +0,0 @@
|
||||
var helpers = require("marko/src/runtime/vdom/helpers");
|
||||
|
||||
console.log("HELPERS:", helpers);
|
||||
@ -1,34 +0,0 @@
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
import commonjsPlugin from "rollup-plugin-commonjs";
|
||||
import browserifyPlugin from "rollup-plugin-browserify-transform";
|
||||
import nodeResolvePlugin from "rollup-plugin-node-resolve";
|
||||
import markoify from "markoify";
|
||||
import envify from "envify";
|
||||
import minpropsify from "minprops/browserify";
|
||||
import path from "path";
|
||||
|
||||
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
|
||||
|
||||
export default {
|
||||
entry: path.join(__dirname, "client.js"),
|
||||
format: "iife",
|
||||
moduleName: "app",
|
||||
plugins: [
|
||||
browserifyPlugin(markoify),
|
||||
browserifyPlugin(envify),
|
||||
browserifyPlugin(minpropsify),
|
||||
nodeResolvePlugin({
|
||||
jsnext: false, // Default: false
|
||||
main: true, // Default: true
|
||||
browser: true, // Default: false
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".marko"]
|
||||
}),
|
||||
commonjsPlugin({
|
||||
include: [],
|
||||
extensions: [".js", ".marko"]
|
||||
})
|
||||
],
|
||||
dest: path.join(__dirname, "../build/bundles/marko.js")
|
||||
};
|
||||
@ -1,149 +0,0 @@
|
||||
console.log("Minifying JavaScript bundles...");
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const UglifyJS = require("uglify-js");
|
||||
const formatNumber = require("format-number")();
|
||||
|
||||
var buildDir = path.join(__dirname, "build");
|
||||
var bundlesDir = path.join(__dirname, "build/bundles");
|
||||
var bundlesMinDir = path.join(__dirname, "build/bundles.min");
|
||||
|
||||
try {
|
||||
fs.mkdirSync(bundlesMinDir);
|
||||
} catch (e) {
|
||||
/* ignore error */
|
||||
}
|
||||
|
||||
var promiseChain = Promise.resolve();
|
||||
|
||||
function getVersion(name) {
|
||||
return require(name + "/package.json").version;
|
||||
}
|
||||
|
||||
function leftPad(str, padding) {
|
||||
if (str.length < padding) {
|
||||
str = new Array(padding - str.length).join(" ") + str;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
var minifiers = {
|
||||
gcc: function minifyGCC(src, file) {
|
||||
const gcc = require("google-closure-compiler-js");
|
||||
const options = {
|
||||
jsCode: [{ src: src }],
|
||||
languageIn: "ES5"
|
||||
};
|
||||
|
||||
const out = gcc.compile(options);
|
||||
|
||||
if (out.errors && out.errors.length) {
|
||||
console.error(out.errors);
|
||||
throw new Error(`Minification failed for ${file}`);
|
||||
}
|
||||
return out.compiledCode;
|
||||
},
|
||||
uglify: function minifyUglifyJS(src, file) {
|
||||
try {
|
||||
return UglifyJS.minify(src, {
|
||||
fromString: true
|
||||
}).code;
|
||||
} catch (e) {
|
||||
if (e.line != null) {
|
||||
console.error(`Failed to minify ${file}`);
|
||||
console.error(` Location: ${file}:${e.line}:${e.col}`);
|
||||
console.error(` Message: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
both: function(src, file) {
|
||||
var withGCC = minifiers.gcc(src, file);
|
||||
var withBoth = minifiers.uglify(withGCC, file);
|
||||
return withBoth.length < withGCC.length ? withBoth : withGCC;
|
||||
}
|
||||
};
|
||||
|
||||
var minifier = minifiers.both;
|
||||
|
||||
var bundleFiles = fs.readdirSync(bundlesDir);
|
||||
|
||||
var sizes = {};
|
||||
|
||||
var targetLib = process.argv[2];
|
||||
|
||||
bundleFiles.forEach(filename => {
|
||||
if (!filename.endsWith(".js")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = path.join(bundlesDir, filename);
|
||||
var ext = path.extname(filename);
|
||||
var lib = filename.slice(0, 0 - ext.length);
|
||||
|
||||
if (targetLib && lib !== targetLib) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Minifying ${file}...`);
|
||||
|
||||
var src = fs.readFileSync(file, { encoding: "utf8" });
|
||||
|
||||
var minifiedSrc = minifier(src, file);
|
||||
|
||||
console.log(`Done minifying ${file}`);
|
||||
|
||||
var minFile = path.join(bundlesMinDir, filename);
|
||||
fs.writeFileSync(minFile, minifiedSrc, { encoding: "utf8" });
|
||||
|
||||
var sizeInfo = (sizes[lib] = {});
|
||||
|
||||
promiseChain = promiseChain.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Compressing and calculating size of ${file}...`);
|
||||
zlib.gzip(minifiedSrc, function(err, gzippedBuffer) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
// Compare the sizes
|
||||
var minifiedBuffer = new Buffer(minifiedSrc, "utf8");
|
||||
// console.log(nodePath.basename(templateInfo.outputCompileMinifiedFile) + ': ' + gzippedBuffer.length + ' bytes gzipped (' + minifiedBuffer.length + ' bytes uncompressed)');
|
||||
|
||||
sizeInfo.gzipped = gzippedBuffer.length;
|
||||
sizeInfo.min = minifiedBuffer.length;
|
||||
|
||||
var libVersion = getVersion(lib);
|
||||
var sizeFilename = lib + (libVersion ? "-" + libVersion : "") + ".json";
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(buildDir, sizeFilename),
|
||||
JSON.stringify(sizeInfo, null, 4),
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
promiseChain.then(() => {
|
||||
console.log();
|
||||
|
||||
for (var lib in sizes) {
|
||||
var sizeInfo = sizes[lib];
|
||||
console.log("[" + lib + "]");
|
||||
console.log(
|
||||
" gzip: " + leftPad(formatNumber(sizeInfo.gzipped), 8) + " bytes"
|
||||
);
|
||||
console.log(" min: " + leftPad(formatNumber(sizeInfo.min), 8) + " bytes");
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log("Minification complete.");
|
||||
});
|
||||
@ -1,70 +0,0 @@
|
||||
{
|
||||
"name": "size-benchmark",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"setup": "npm install --silent && npm link ../../",
|
||||
"build": "npm run bundle --silent && npm run minify --silent",
|
||||
"build-marko": "npm run bundle-marko --silent && node minify.js marko",
|
||||
"build-vue": "npm run bundle-vue --silent && node minify.js vue",
|
||||
"build-react": "npm run bundle-react --silent && node minify.js react",
|
||||
"build-inferno":
|
||||
"npm run bundle-inferno --silent && node minify.js inferno",
|
||||
"bundle":
|
||||
"mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact && npm run bundle-inferno",
|
||||
"bundle-marko":
|
||||
"node ../../scripts/build src && NODE_ENV=production rollup -c marko/rollup.config.js",
|
||||
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
|
||||
"bundle-preact":
|
||||
"NODE_ENV=production rollup -c preact/rollup.config.js",
|
||||
"bundle-vue": "NODE_ENV=production rollup -c vue/rollup.config.js",
|
||||
"bundle-inferno":
|
||||
"NODE_ENV=production rollup -c inferno/rollup.config.js",
|
||||
"minify": "node minify.js",
|
||||
"http-server": "http-server"
|
||||
},
|
||||
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel-plugin-inferno": "^1.8.0",
|
||||
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
|
||||
"babel-plugin-transform-react-constant-elements": "^6.9.1",
|
||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-es2015-loose": "^8.0.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-0": "^6.16.0",
|
||||
"babelify": "^7.3.0",
|
||||
"envify": "^4.0.0",
|
||||
"format-number": "^2.0.1",
|
||||
"google-closure-compiler-js": "^20161201.0.0",
|
||||
"http-server": "^0.9.0",
|
||||
"inferno": "^1.3.0-rc.1",
|
||||
"inferno-component": "^1.3.0-rc.1",
|
||||
"inferno-server": "^1.3.0-rc.1",
|
||||
"markoify": "^2.1.1",
|
||||
"minprops": "^1.0.0",
|
||||
"preact": "^7.1.0",
|
||||
"react": "^15.4.1",
|
||||
"react-dom": "^15.4.1",
|
||||
"rollup": "^0.41.6",
|
||||
"rollup-plugin-babel": "^2.7.1",
|
||||
"rollup-plugin-browserify-transform": "^0.1.0",
|
||||
"rollup-plugin-commonjs": "^8.0.2",
|
||||
"rollup-plugin-marko": "0.0.2",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"uglify-js": "^2.7.5",
|
||||
"vue": "^2.1.6",
|
||||
"vueify": "^9.4.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marko-js/marko.git"
|
||||
},
|
||||
"browser": {
|
||||
"events": "events-light"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Preact</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Preact</h1>
|
||||
<script src="./build/bundles.min/preact.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["es2015", { "loose": true, "modules": false }],
|
||||
"stage-0"
|
||||
],
|
||||
"plugins": [
|
||||
[ "transform-react-jsx", { "pragma": "h" } ],
|
||||
["transform-es2015-block-scoping"]
|
||||
]
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
var preact = require('preact');
|
||||
var h = preact.h;
|
||||
var render = preact.render;
|
||||
var App = require('./components/App');
|
||||
|
||||
render(
|
||||
<App name='Frank' colors={['red', 'green', 'blue']}/>,
|
||||
document.body);
|
||||
@ -1,58 +0,0 @@
|
||||
'use strict';
|
||||
var preact = require('preact');
|
||||
var h = preact.h;
|
||||
var Component = preact.Component;
|
||||
|
||||
function renderColor(color) {
|
||||
var style = {
|
||||
backgroundColor: color
|
||||
};
|
||||
|
||||
return <li className="color" style={style}>
|
||||
{color}
|
||||
</li>
|
||||
}
|
||||
|
||||
function renderColors(colors) {
|
||||
if (colors.length) {
|
||||
return (<ul>{colors.map(renderColor)}</ul>);
|
||||
} else {
|
||||
return <div>No colors!</div>
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: props.name,
|
||||
colors: props.colors,
|
||||
clickCount: 0
|
||||
}
|
||||
|
||||
this.handleButtonClick = function() {
|
||||
this.setState({
|
||||
clickCount: this.state.clickCount + 1
|
||||
});
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
var colors = this.state.colors;
|
||||
var name = this.state.name;
|
||||
var clickCount = this.state.clickCount;
|
||||
var handleButtonClick = this.handleButtonClick;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello {name}!</h1>
|
||||
<div className="colors">
|
||||
{renderColors(colors)}
|
||||
</div>
|
||||
<button type="button" onClick={handleButtonClick}>
|
||||
You clicked the button {clickCount} time(s)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
import commonjsPlugin from "rollup-plugin-commonjs";
|
||||
import browserifyPlugin from "rollup-plugin-browserify-transform";
|
||||
import nodeResolvePlugin from "rollup-plugin-node-resolve";
|
||||
import babelPlugin from "rollup-plugin-babel";
|
||||
import envify from "envify";
|
||||
import path from "path";
|
||||
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
|
||||
|
||||
export default {
|
||||
entry: path.join(__dirname, "client.jsx"),
|
||||
format: "iife",
|
||||
moduleName: "app",
|
||||
plugins: [
|
||||
babelPlugin({
|
||||
// include: ['node_modules/**', '**/*.js', '**/*.jsx']
|
||||
}),
|
||||
browserifyPlugin(envify),
|
||||
nodeResolvePlugin({
|
||||
jsnext: false, // Default: false
|
||||
main: true, // Default: true
|
||||
browser: true, // Default: false
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".jsx"]
|
||||
}),
|
||||
commonjsPlugin({
|
||||
include: [],
|
||||
extensions: [".js", ".jsx"]
|
||||
})
|
||||
],
|
||||
dest: path.join(__dirname, "../build/bundles/preact.js")
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>React</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>React</h1>
|
||||
<script src="./build/bundles.min/react.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["es2015", { "loose": true, "modules": false }],
|
||||
"stage-0",
|
||||
"react"
|
||||
],
|
||||
"plugins": ["transform-react-constant-elements"]
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var App = require('./components/App');
|
||||
|
||||
ReactDOM.render(
|
||||
<App name='Frank' colors={['red', 'green', 'blue']}/>,
|
||||
document.body);
|
||||
@ -1,56 +0,0 @@
|
||||
'use strict';
|
||||
var React = require('react');
|
||||
|
||||
function renderColor(color) {
|
||||
var style = {
|
||||
backgroundColor: color
|
||||
};
|
||||
|
||||
return <li className="color" style={style}>
|
||||
{color}
|
||||
</li>
|
||||
}
|
||||
|
||||
function renderColors(colors) {
|
||||
if (colors.length) {
|
||||
return (<ul>{colors.map(renderColor)}</ul>);
|
||||
} else {
|
||||
return <div>No colors!</div>
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: props.name,
|
||||
colors: props.colors,
|
||||
clickCount: 0
|
||||
}
|
||||
|
||||
this.handleButtonClick = function() {
|
||||
this.setState({
|
||||
clickCount: this.state.clickCount + 1
|
||||
});
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
var colors = this.state.colors;
|
||||
var name = this.state.name;
|
||||
var clickCount = this.state.clickCount;
|
||||
var handleButtonClick = this.handleButtonClick;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello {name}!</h1>
|
||||
<div className="colors">
|
||||
{renderColors(colors)}
|
||||
</div>
|
||||
<button type="button" onClick={handleButtonClick}>
|
||||
You clicked the button {clickCount} time(s)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
import commonjsPlugin from "rollup-plugin-commonjs";
|
||||
import browserifyPlugin from "rollup-plugin-browserify-transform";
|
||||
import nodeResolvePlugin from "rollup-plugin-node-resolve";
|
||||
import babelPlugin from "rollup-plugin-babel";
|
||||
import envify from "envify";
|
||||
import path from "path";
|
||||
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
|
||||
|
||||
export default {
|
||||
entry: path.join(__dirname, "client.jsx"),
|
||||
format: "iife",
|
||||
moduleName: "app",
|
||||
plugins: [
|
||||
babelPlugin({
|
||||
exclude: "node_modules/**"
|
||||
}),
|
||||
browserifyPlugin(envify),
|
||||
nodeResolvePlugin({
|
||||
jsnext: true, // Default: false
|
||||
main: true, // Default: true
|
||||
browser: true, // Default: false
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".jsx"]
|
||||
}),
|
||||
commonjsPlugin({
|
||||
include: [],
|
||||
extensions: [".js", ".jsx"]
|
||||
})
|
||||
],
|
||||
dest: path.join(__dirname, "../build/bundles/react.js")
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Vue</h1>
|
||||
<script src="./build/bundles.min/vue.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,17 +0,0 @@
|
||||
var Vue = require("vue");
|
||||
var App = require("./components/App");
|
||||
|
||||
// var app = new App({
|
||||
// el: document.body,
|
||||
// data: {
|
||||
// name: 'Frank',
|
||||
// colors: ['red', 'green', 'blue']
|
||||
// }
|
||||
// });
|
||||
|
||||
new Vue({
|
||||
el: document.body,
|
||||
render: function(createElement) {
|
||||
return createElement(App);
|
||||
}
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>
|
||||
Hello {{name}}!
|
||||
</h1>
|
||||
|
||||
<div class="colors">
|
||||
<ul v-if="colors.length">
|
||||
<li v-for="color in colors" class="color" v-bind:style="'background-color: ' + color">
|
||||
{{color}}
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else>
|
||||
No colors!
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" v-on:click="clickCount += 1">
|
||||
You clicked the button {{clickCount}} time(s)
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
data: function() {
|
||||
return {
|
||||
clickCount: 0,
|
||||
name: 'Frank',
|
||||
colors: ['red', 'green', 'blue']
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleButtonClick: function() {
|
||||
this.clickCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,32 +0,0 @@
|
||||
import commonjsPlugin from "rollup-plugin-commonjs";
|
||||
import browserifyPlugin from "rollup-plugin-browserify-transform";
|
||||
import nodeResolvePlugin from "rollup-plugin-node-resolve";
|
||||
import vueify from "vueify";
|
||||
import envify from "envify";
|
||||
import minpropsify from "minprops/browserify";
|
||||
import path from "path";
|
||||
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
export default {
|
||||
entry: path.join(__dirname, "client.js"),
|
||||
format: "iife",
|
||||
moduleName: "app",
|
||||
plugins: [
|
||||
browserifyPlugin(vueify),
|
||||
browserifyPlugin(envify),
|
||||
browserifyPlugin(minpropsify),
|
||||
nodeResolvePlugin({
|
||||
jsnext: true, // Default: false
|
||||
main: true, // Default: true
|
||||
browser: true, // Default: false
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".vue"]
|
||||
}),
|
||||
commonjsPlugin({
|
||||
include: [],
|
||||
extensions: [".js", ".vue"]
|
||||
})
|
||||
],
|
||||
dest: path.join(__dirname, "../build/bundles/vue.js")
|
||||
};
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
module.exports = function(app) {
|
||||
var Suite = window.Benchmark.Suite;
|
||||
|
||||
var names = ["dom", "dom-innerHTML", "marko-vdom", "react"];
|
||||
|
||||
var htmlFiles = ["todomvc", "marko-docs", "tabs"];
|
||||
|
||||
function loadScripts() {
|
||||
window.createBenchmarks = {};
|
||||
|
||||
var scripts = [];
|
||||
|
||||
names.forEach(function(name) {
|
||||
htmlFiles.forEach(function(htmlFile) {
|
||||
scripts.push(
|
||||
"./codegen-create/benchmark-" + htmlFile + "-" + name + ".js"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return app.loadScripts(scripts);
|
||||
}
|
||||
|
||||
function runForHtmlFile(htmlFile) {
|
||||
return loadScripts(htmlFile).then(function() {
|
||||
var suite = new Suite("create-" + htmlFile);
|
||||
|
||||
names.forEach(function(name) {
|
||||
suite.add(name, function() {
|
||||
return window.createBenchmarks[htmlFile + "-" + name]();
|
||||
});
|
||||
});
|
||||
|
||||
return app.runSuite(suite);
|
||||
});
|
||||
}
|
||||
|
||||
var loadScriptsPromise = loadScripts();
|
||||
|
||||
return function() {
|
||||
var promiseChain = loadScriptsPromise;
|
||||
|
||||
htmlFiles.forEach(function(htmlFile) {
|
||||
promiseChain = promiseChain.then(function() {
|
||||
return runForHtmlFile(htmlFile);
|
||||
});
|
||||
});
|
||||
|
||||
return promiseChain;
|
||||
};
|
||||
};
|
||||
@ -1,93 +0,0 @@
|
||||
module.exports = function(app) {
|
||||
var Suite = window.Benchmark.Suite;
|
||||
var MarkoVDOM = app.vdom;
|
||||
|
||||
var suite = new Suite("walk");
|
||||
|
||||
var todomvcDOM = document.getElementById("todoapp");
|
||||
var todomvcDOMVirtual = MarkoVDOM.virtualize(todomvcDOM);
|
||||
|
||||
function toHTML(node) {
|
||||
// NOTE: We don't use XMLSerializer because we need to sort the attributes to correctly compare output HTML strings
|
||||
// BAD: return (new XMLSerializer()).serializeToString(node);
|
||||
var html = "";
|
||||
function serializeHelper(node, indent) {
|
||||
if (node.nodeType === 1) {
|
||||
serializeElHelper(node, indent);
|
||||
} else if (node.nodeType === 3) {
|
||||
serializeTextHelper(node, indent);
|
||||
} else if (node.nodeType === 8) {
|
||||
serializeCommentHelper(node, indent);
|
||||
} else {
|
||||
console.log("Invalid node:", node);
|
||||
html += indent + `INVALID NODE TYPE ${node.nodeType}\n`;
|
||||
// throw new Error('Unexpected node type');
|
||||
}
|
||||
}
|
||||
|
||||
function serializeElHelper(el, indent) {
|
||||
var tagName = el.nodeName;
|
||||
|
||||
if (el.namespaceURI === "http://www.w3.org/2000/svg") {
|
||||
tagName = "svg:" + tagName;
|
||||
} else if (el.namespaceURI === "http://www.w3.org/1998/Math/MathML") {
|
||||
tagName = "math:" + tagName;
|
||||
}
|
||||
|
||||
html += indent + "<" + tagName;
|
||||
|
||||
var attributes = el.attributes;
|
||||
var attributesArray = [];
|
||||
|
||||
for (var i = 0; i < attributes.length; i++) {
|
||||
var attr = attributes[i];
|
||||
var attrName = attr.name;
|
||||
if (attr.namespaceURI) {
|
||||
attrName = attr.namespaceURI + ":" + attrName;
|
||||
}
|
||||
attributesArray.push(" " + attrName + '="' + attr.value + '"');
|
||||
}
|
||||
|
||||
attributesArray.sort();
|
||||
|
||||
html += attributesArray.join("");
|
||||
|
||||
html += ">\n";
|
||||
|
||||
if (tagName.toUpperCase() === "TEXTAREA") {
|
||||
html += indent + " VALUE: " + JSON.stringify(el.value) + "\n";
|
||||
} else {
|
||||
var curChild = el.firstChild;
|
||||
while (curChild) {
|
||||
serializeHelper(curChild, indent + " ");
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serializeTextHelper(node, indent) {
|
||||
html += indent + JSON.stringify(node.nodeValue) + "\n";
|
||||
}
|
||||
|
||||
function serializeCommentHelper(node, indent) {
|
||||
html += indent + "<!--" + JSON.stringify(node.nodeValue) + "-->\n";
|
||||
}
|
||||
|
||||
serializeHelper(node, "");
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// add tests
|
||||
suite.add("real DOM", function() {
|
||||
return toHTML(todomvcDOM);
|
||||
});
|
||||
|
||||
suite.add("marko-vdom", function() {
|
||||
return toHTML(todomvcDOMVirtual);
|
||||
});
|
||||
|
||||
return function() {
|
||||
return app.runSuite(suite);
|
||||
};
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"dependencies": [
|
||||
{
|
||||
"type": "js",
|
||||
"url": "https://unpkg.com/react@15.3.1/dist/react.min.js"
|
||||
},
|
||||
{
|
||||
"type": "js",
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.js"
|
||||
},
|
||||
{
|
||||
"type": "js",
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.2/benchmark.js"
|
||||
},
|
||||
"benchmark/benchmark.js",
|
||||
"require-run: ./client.js"
|
||||
]
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
var HTMLElement = require("../../runtime/vdom/HTMLElement");
|
||||
var Text = require("../../runtime/vdom/Text");
|
||||
var Comment = require("../../runtime/vdom/Comment");
|
||||
var DocumentFragment = require("../../runtime/vdom/DocumentFragment");
|
||||
|
||||
var resultsEl = document.getElementById("results");
|
||||
var running = false;
|
||||
var benchmarks = {};
|
||||
|
||||
function loadScript(path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var script = document.createElement("script");
|
||||
script.src = path;
|
||||
script.onload = function() {
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = function(e) {
|
||||
reject(e);
|
||||
};
|
||||
|
||||
document.head.appendChild(script); //or something of the likes
|
||||
});
|
||||
}
|
||||
|
||||
function loadScripts(paths) {
|
||||
return Promise.all(
|
||||
paths.map(function(path) {
|
||||
return loadScript(path);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function runSuite(suite) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
suite
|
||||
.on("start", function() {
|
||||
resultsEl.innerHTML += 'Running "' + suite.name + '"...\n';
|
||||
})
|
||||
.on("cycle", function(event) {
|
||||
resultsEl.innerHTML += String(event.target) + "\n";
|
||||
})
|
||||
.on("complete", function() {
|
||||
resultsEl.innerHTML +=
|
||||
"Fastest is " +
|
||||
this.filter("fastest").map("name") +
|
||||
"\n\n--------------\n\n";
|
||||
|
||||
running = false;
|
||||
|
||||
suite.off("start cycle complete");
|
||||
resolve();
|
||||
})
|
||||
.on("error", function(e) {
|
||||
running = false;
|
||||
|
||||
suite.off("start cycle complete error");
|
||||
reject(e.target.error);
|
||||
})
|
||||
// run async
|
||||
.run({ async: true });
|
||||
});
|
||||
}
|
||||
|
||||
var vdom = (window.MarkoVDOM = {
|
||||
virtualize: require("../../runtime/vdom/virtualize"),
|
||||
|
||||
createElement: function(tagName, attrs, childCount, constId) {
|
||||
return new HTMLElement(tagName, attrs, childCount, constId);
|
||||
},
|
||||
createText: function(value) {
|
||||
return new Text(value);
|
||||
},
|
||||
createComment: function(value) {
|
||||
return new Comment(value);
|
||||
},
|
||||
createDocumentFragment: function() {
|
||||
return new DocumentFragment();
|
||||
}
|
||||
});
|
||||
|
||||
var app = {
|
||||
loadScript,
|
||||
loadScripts,
|
||||
runSuite,
|
||||
vdom
|
||||
};
|
||||
|
||||
function registerBenchmark(name, func) {
|
||||
benchmarks[name] = func(app);
|
||||
}
|
||||
|
||||
registerBenchmark("create", require("./benchmark-create"));
|
||||
registerBenchmark("walk", require("./benchmark-walk"));
|
||||
|
||||
document.body.addEventListener("click", function(event) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
var target = event.target;
|
||||
var benchmarkName = target.getAttribute("data-benchmark");
|
||||
if (benchmarkName) {
|
||||
var oldButtonLabel = target.innerHTML;
|
||||
target.innerHTML = oldButtonLabel + " - running...";
|
||||
resultsEl.innerHTML = "";
|
||||
|
||||
var benchmarkFunc = benchmarks[benchmarkName];
|
||||
|
||||
benchmarkFunc()
|
||||
.then(function() {
|
||||
target.innerHTML = oldButtonLabel;
|
||||
|
||||
resultsEl.innerHTML += "\nDONE!";
|
||||
})
|
||||
.catch(function(e) {
|
||||
target.innerHTML = oldButtonLabel;
|
||||
console.error(e);
|
||||
resultsEl.innerHTML = e.toString();
|
||||
});
|
||||
}
|
||||
});
|
||||
1
benchmark/vdom/codegen-create/.gitignore
vendored
1
benchmark/vdom/codegen-create/.gitignore
vendored
@ -1 +0,0 @@
|
||||
benchmark-*
|
||||
@ -1,13 +0,0 @@
|
||||
module.exports = function() {
|
||||
return `
|
||||
var fragment = range.createContextualFragment(html);
|
||||
return fragment.childNodes[0];`;
|
||||
};
|
||||
|
||||
module.exports.generateInitCode = function(node, html) {
|
||||
return `
|
||||
var range = document.createRange();
|
||||
range.selectNode(document.body);
|
||||
var html = ${JSON.stringify(html)};
|
||||
`;
|
||||
};
|
||||
@ -1,44 +0,0 @@
|
||||
module.exports = function(node) {
|
||||
var nextId = 0;
|
||||
|
||||
var code = "";
|
||||
|
||||
function codegenEl(node, level) {
|
||||
var varName = level === 0 ? "root" : `node${nextId++}`;
|
||||
|
||||
code += `var ${varName} = document.createElement(${JSON.stringify(
|
||||
node.nodeName
|
||||
)})\n`;
|
||||
|
||||
var attributes = node.attributes;
|
||||
|
||||
for (var i = 0; i < attributes.length; i++) {
|
||||
var attr = attributes[i];
|
||||
code += `${varName}.setAttribute(${JSON.stringify(
|
||||
attr.name
|
||||
)}, ${JSON.stringify(attr.value)})\n`;
|
||||
}
|
||||
|
||||
var curChild = node.firstChild;
|
||||
while (curChild) {
|
||||
if (curChild.nodeType === 1) {
|
||||
var childVarName = codegenEl(curChild, level + 1);
|
||||
code += `${varName}.appendChild(${childVarName})\n`;
|
||||
} else if (curChild.nodeType === 3) {
|
||||
code += `${varName}.appendChild(document.createTextNode(${JSON.stringify(
|
||||
curChild.nodeValue
|
||||
)}))\n`;
|
||||
}
|
||||
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
|
||||
return varName;
|
||||
}
|
||||
|
||||
codegenEl(node, 0);
|
||||
|
||||
code += "\nreturn root;\n";
|
||||
|
||||
return code;
|
||||
};
|
||||
@ -1,63 +0,0 @@
|
||||
function indentStr(level) {
|
||||
var str = "";
|
||||
for (var i = 0; i < level; i++) {
|
||||
str += " ";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
module.exports = function(node) {
|
||||
var code = "";
|
||||
|
||||
function codegenEl(node, level) {
|
||||
if (level === 0) {
|
||||
code += "createElement";
|
||||
} else {
|
||||
code += ".e";
|
||||
}
|
||||
|
||||
var attrsMap = {};
|
||||
|
||||
var attributes = node.attributes;
|
||||
for (var i = 0; i < attributes.length; i++) {
|
||||
var attr = attributes[i];
|
||||
attrsMap[attr.name] = attr.value;
|
||||
}
|
||||
|
||||
code += `(${JSON.stringify(node.nodeName)}, ${JSON.stringify(attrsMap)}, ${
|
||||
node.childNodes.length
|
||||
})\n`;
|
||||
// codegenAttrs(node.attributes, level + 1);
|
||||
|
||||
var curChild = node.firstChild;
|
||||
while (curChild) {
|
||||
codegen(curChild, level + 1);
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function codegenText(node) {
|
||||
code += `.t(${JSON.stringify(node.nodeValue)})\n`;
|
||||
}
|
||||
|
||||
function codegen(node, level) {
|
||||
code += indentStr(level);
|
||||
|
||||
if (node.nodeType === 1) {
|
||||
codegenEl(node, level);
|
||||
} else if (node.nodeType === 3) {
|
||||
codegenText(node, level);
|
||||
}
|
||||
}
|
||||
|
||||
codegen(node, 0);
|
||||
|
||||
return "return " + code + "\n";
|
||||
};
|
||||
|
||||
module.exports.generateInitCode = function() {
|
||||
return `
|
||||
var MarkoVDOM = window.MarkoVDOM;
|
||||
var createElement = MarkoVDOM.createElement;
|
||||
`;
|
||||
};
|
||||
@ -1,69 +0,0 @@
|
||||
function indentStr(level) {
|
||||
var str = "";
|
||||
for (var i = 0; i < level; i++) {
|
||||
str += " ";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
module.exports = function(node) {
|
||||
function codegenChildNodes(node, level) {
|
||||
var curChild = node.firstChild;
|
||||
if (!curChild) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
var code = "[\n";
|
||||
|
||||
var childLevel = level + 2;
|
||||
while (curChild) {
|
||||
code += indentStr(childLevel) + codegen(curChild, childLevel);
|
||||
curChild = curChild.nextSibling;
|
||||
|
||||
if (curChild) {
|
||||
code += ",\n";
|
||||
} else {
|
||||
code += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
code += indentStr(level + 1) + "]";
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
function codegenEl(node, level) {
|
||||
var attributesMap = null;
|
||||
|
||||
var attributes = node.attributes;
|
||||
if (attributes.length) {
|
||||
attributesMap = {};
|
||||
|
||||
for (var i = 0; i < attributes.length; i++) {
|
||||
var attr = attributes[i];
|
||||
var attrName = attr.name;
|
||||
var attrValue = attr.value;
|
||||
|
||||
if (attrName === "class") {
|
||||
attrName = "className";
|
||||
}
|
||||
attributesMap[attrName] = attrValue;
|
||||
}
|
||||
}
|
||||
|
||||
return `React.createElement(${JSON.stringify(
|
||||
node.nodeName
|
||||
)}, ${JSON.stringify(attributesMap)}, ${codegenChildNodes(node, level)})`;
|
||||
}
|
||||
|
||||
function codegen(node, level) {
|
||||
if (node.nodeType === 1) {
|
||||
return codegenEl(node, level);
|
||||
} else if (node.nodeType === 3) {
|
||||
return JSON.stringify(node.nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
return "return " + codegen(node, 0) + "\n";
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@ -1,19 +0,0 @@
|
||||
<div>
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
|
||||
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
|
||||
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="home">...</div>
|
||||
<div role="tabpanel" class="tab-pane" id="profile">...</div>
|
||||
<div role="tabpanel" class="tab-pane" id="messages">...</div>
|
||||
<div role="tabpanel" class="tab-pane" id="settings">...</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -1,82 +0,0 @@
|
||||
<section id="todoapp">
|
||||
<div id="w0">
|
||||
<header id="header">
|
||||
<h1>todos</h1>
|
||||
<form data-w-onsubmit="handleFormSubmit|header">
|
||||
<input id="new-todo" placeholder="What needs to be done?">
|
||||
</form>
|
||||
</header>
|
||||
<section id="main">
|
||||
<input id="toggle-all" type="checkbox" data-w-onchange="handleToggleAllOnChange|main">
|
||||
<label for="toggle-all">Mark all as complete</label>
|
||||
<ul id="todo-list">
|
||||
<li id="main-todo-0" class="completed">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-0" checked="">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-0">Go to the grocery store FOO</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-0"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-0-titleInput" data-w-onchange="handleInputChange|main-todo-0" data-w-onkeydown="handleInputKeyDown|main-todo-0">
|
||||
</li>
|
||||
<li id="main-todo-1">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-1">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-1">Ship item</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-1"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-1-titleInput" data-w-onchange="handleInputChange|main-todo-1" data-w-onkeydown="handleInputKeyDown|main-todo-1">
|
||||
</li>
|
||||
<li id="main-todo-2">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-2">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-2">Respond to email</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-2"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-2-titleInput" data-w-onchange="handleInputChange|main-todo-2" data-w-onkeydown="handleInputKeyDown|main-todo-2">
|
||||
</li>
|
||||
<li id="main-todo-3">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-3">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-3">Foo</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-3"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-3-titleInput" data-w-onchange="handleInputChange|main-todo-3" data-w-onkeydown="handleInputKeyDown|main-todo-3">
|
||||
</li>
|
||||
<li id="main-todo-4" class="completed">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-4" checked="">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-4">Bar</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-4"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-4-titleInput" data-w-onchange="handleInputChange|main-todo-4" data-w-onkeydown="handleInputKeyDown|main-todo-4">
|
||||
</li>
|
||||
<li id="main-todo-5">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-5">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-5">Baz</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-5"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-5-titleInput" data-w-onchange="handleInputChange|main-todo-5" data-w-onkeydown="handleInputKeyDown|main-todo-5">
|
||||
</li>
|
||||
<li id="main-todo-6">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-6">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-6">Test</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-6"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-6-titleInput" data-w-onchange="handleInputChange|main-todo-6" data-w-onkeydown="handleInputKeyDown|main-todo-6">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer id="footer">
|
||||
<span id="todo-count">
|
||||
<strong>5</strong> items left</span>
|
||||
<ul id="filters">
|
||||
<li><a href="#/" class="selected" data-w-onclick="handleAllFilterClick|footer">All</a></li>
|
||||
<li><a href="#/active" data-w-onclick="handleActiveFilterClick|footer">Active</a></li>
|
||||
<li><a href="#/completed" data-w-onclick="handleCompletedFilterClick|footer">Completed</a></li>
|
||||
</ul>
|
||||
<button id="clear-completed" data-w-onclick="handleClearCompletedClick|footer">Clear completed (2)</button>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,48 +0,0 @@
|
||||
var jsdom = require("jsdom").jsdom;
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
function generateCode(name, htmlFile, rootNode, html) {
|
||||
var generator = require("./codegen-" + name);
|
||||
|
||||
var code = generator(rootNode, html);
|
||||
var wrappedCode = `window.createBenchmarks[${JSON.stringify(
|
||||
htmlFile + "-" + name
|
||||
)}]=function() {\n${code}\n}`;
|
||||
|
||||
var generateInitCode = generator.generateInitCode;
|
||||
|
||||
if (generateInitCode) {
|
||||
wrappedCode = `
|
||||
(function() {
|
||||
${generateInitCode(rootNode, html)}
|
||||
${wrappedCode}
|
||||
}())`;
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `benchmark-${htmlFile}-${name}.js`),
|
||||
wrappedCode,
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
}
|
||||
|
||||
var methods = ["dom", "dom-innerHTML", "marko-vdom", "react"];
|
||||
|
||||
var htmlFiles = fs.readdirSync(__dirname).filter(function(name) {
|
||||
return name.startsWith("html-");
|
||||
});
|
||||
|
||||
htmlFiles.forEach(function(htmlFile) {
|
||||
var name = htmlFile.substring("html-".length).slice(0, 0 - ".html".length);
|
||||
var html = fs.readFileSync(path.join(__dirname, htmlFile), {
|
||||
encoding: "utf8"
|
||||
});
|
||||
|
||||
var doc = jsdom(html);
|
||||
|
||||
var rootNode = doc.body.firstChild;
|
||||
|
||||
methods.forEach(function(methodName) {
|
||||
generateCode(methodName, name, rootNode, html);
|
||||
});
|
||||
});
|
||||
@ -1,98 +0,0 @@
|
||||
<lasso-page package-path="./browser.json"/>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>marko-vdom benchmarks</title>
|
||||
<lasso-head/>
|
||||
</head>
|
||||
<body>
|
||||
<button type="button" data-benchmark="walk">
|
||||
Run benchmark: walk
|
||||
</button>
|
||||
|
||||
<button type="button" data-benchmark="create">
|
||||
Run benchmark: create
|
||||
</button>
|
||||
|
||||
<pre id="results" style="width: 100%; border: 1px solid black;">
|
||||
</pre>
|
||||
|
||||
<section id="todoapp" style="display: none">
|
||||
<div id="w0">
|
||||
<header id="header">
|
||||
<h1>todos</h1>
|
||||
<form data-w-onsubmit="handleFormSubmit|header">
|
||||
<input id="new-todo" placeholder="What needs to be done?">
|
||||
</form>
|
||||
</header>
|
||||
<section id="main">
|
||||
<input id="toggle-all" type="checkbox" data-w-onchange="handleToggleAllOnChange|main">
|
||||
<label for="toggle-all">Mark all as complete</label>
|
||||
<ul id="todo-list">
|
||||
<li id="main-todo-0">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-0">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-0">Go to the grocery store</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-0"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-0-titleInput" data-w-onchange="handleInputChange|main-todo-0" data-w-onkeydown="handleInputKeyDown|main-todo-0">
|
||||
</li>
|
||||
<li class="completed" id="main-todo-1">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" checked="" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-1">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-1">Ship item</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-1"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-1-titleInput" data-w-onchange="handleInputChange|main-todo-1" data-w-onkeydown="handleInputKeyDown|main-todo-1">
|
||||
</li>
|
||||
<li id="main-todo-2">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-2">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-2">Respond to email</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-2"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-2-titleInput" data-w-onchange="handleInputChange|main-todo-2" data-w-onkeydown="handleInputKeyDown|main-todo-2">
|
||||
</li>
|
||||
<li id="main-todo-3">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-3">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-3">Foo</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-3"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-3-titleInput" data-w-onchange="handleInputChange|main-todo-3" data-w-onkeydown="handleInputKeyDown|main-todo-3">
|
||||
</li>
|
||||
<li id="main-todo-4">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-4">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-4">Bar</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-4"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-4-titleInput" data-w-onchange="handleInputChange|main-todo-4" data-w-onkeydown="handleInputKeyDown|main-todo-4">
|
||||
</li>
|
||||
<li id="main-todo-5">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" aria-label="Toggle todo completed" data-w-onchange="handleCheckboxChange|main-todo-5">
|
||||
<label data-w-ondblclick="handleLabelDblClick|main-todo-5">Baz</label>
|
||||
<button class="destroy" aria-label="Delete todo" data-w-onclick="handleDestroyClick|main-todo-5"></button>
|
||||
</div>
|
||||
<input title="Enter the new todo title" type="text" class="edit" id="main-todo-5-titleInput" data-w-onchange="handleInputChange|main-todo-5" data-w-onkeydown="handleInputKeyDown|main-todo-5">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer id="footer">
|
||||
<span id="todo-count">
|
||||
<strong>5</strong> items left</span>
|
||||
<ul id="filters">
|
||||
<li><a href="#/" class="selected" data-w-onclick="handleAllFilterClick|footer">All</a></li>
|
||||
<li><a href="#/active" data-w-onclick="handleActiveFilterClick|footer">Active</a></li>
|
||||
<li><a href="#/completed" data-w-onclick="handleCompletedFilterClick|footer">Completed</a></li>
|
||||
</ul>
|
||||
<button id="clear-completed" data-w-onclick="handleClearCompletedClick|footer">Clear completed (1)</button>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
<lasso-body/>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,39 +0,0 @@
|
||||
require("../patch-module");
|
||||
|
||||
require("marko/node-require");
|
||||
require("marko/express");
|
||||
|
||||
var isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
require("lasso").configure({
|
||||
outputDir: __dirname + "/static",
|
||||
bundlingEnabled: isProduction,
|
||||
fingerprintsEnabled: isProduction,
|
||||
minify: isProduction
|
||||
});
|
||||
|
||||
var express = require("express");
|
||||
|
||||
var app = express();
|
||||
|
||||
var serveStatic = require("serve-static");
|
||||
|
||||
require("./codegen-create/run");
|
||||
|
||||
var template = require("./index.marko");
|
||||
|
||||
app.use("/codegen-create", serveStatic(__dirname + "/codegen-create"));
|
||||
|
||||
app.use(require("lasso/middleware").serveStatic());
|
||||
|
||||
app.get("/", function(req, res) {
|
||||
res.marko(template);
|
||||
});
|
||||
|
||||
app.listen(8080, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.log("Server ready:\nhttp://localhost:8080");
|
||||
});
|
||||
@ -1,3 +1,6 @@
|
||||
module.exports = {
|
||||
extends: ["@commitlint/config-conventional"]
|
||||
extends: [
|
||||
"@commitlint/config-conventional",
|
||||
"@commitlint/config-lerna-scopes"
|
||||
]
|
||||
};
|
||||
|
||||
17
lerna.json
Normal file
17
lerna.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"command": {
|
||||
"publish": {
|
||||
"conventionalCommits": true,
|
||||
"message": "chore(release): publish"
|
||||
}
|
||||
},
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"ignoreChanges": [
|
||||
"**/package-lock.json",
|
||||
"packages/*/test/**",
|
||||
"**/*.md"
|
||||
],
|
||||
"version": "independent"
|
||||
}
|
||||
10801
package-lock.json
generated
10801
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
84
package.json
84
package.json
@ -1,49 +1,55 @@
|
||||
{
|
||||
"name": "marko",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"build-src": "node scripts/build.js src",
|
||||
"postinstall": "cd packages/marko && npm i",
|
||||
"release": "npm run build-src && (cd ./packages/marko && standard-version && git push --follow-tags && npm publish)",
|
||||
"test": "mocha --timeout 10000 ./packages/marko/test/*/*.test.js",
|
||||
"test-ci": "npm run lint && npm run check-format && npm run test-generate-coverage",
|
||||
"test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && opn ./coverage/index.html",
|
||||
"test-generate-coverage": "nyc -asc npm test",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier \"**/*.{js,json,css,md}\" --write",
|
||||
"build": "lerna exec --parallel -- babel ./src --out-dir ./dist --delete-dir-on-start --copy-files --config-file ../../babel.config.js --source-maps=inline --env-name=production",
|
||||
"build:watch": "npm run build -- --watch",
|
||||
"postinstall": "lerna bootstrap --hoist --no-ci",
|
||||
"postrelease": "ENTRY=main:dev npm run set-entry",
|
||||
"prerelease": "ENTRY=main:npm npm run set-entry",
|
||||
"release": "npm run build && lerna publish",
|
||||
"test": "mocha -r @babel/register \"packages/*/test/{*.test.js,*/*.test.js}\"",
|
||||
"test:watch": "npm run mocha -- --recursive --watch",
|
||||
"clean": "lerna clean && rm -rf ./packages/*/dist",
|
||||
"lint": "eslint -f visualstudio .",
|
||||
"format": "prettier \"**/*.{json,md,js}\" --write",
|
||||
"check-format": "prettier \"**/*.{js,json,css,md}\" -l",
|
||||
"codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov"
|
||||
"ci:test": "npm run lint && npm run check-format && cross-env MARKO_DEBUG=1 NODE_ENV=test nyc --reporter=text npm test",
|
||||
"ci:codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
||||
"report": "nyc report --reporter=html && open ./coverage/index.html",
|
||||
"set-entry": "lerna exec -- dot-json package.json main $\\(dot-json package.json $ENTRY\\)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^8.3.5",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-core": "^6.24.1",
|
||||
"@babel/cli": "^7.7.7",
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/helper-module-imports": "^7.7.4",
|
||||
"@babel/node": "^7.7.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.7.4",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.7",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/register": "^7.7.7",
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@commitlint/config-conventional": "^8.2.0",
|
||||
"@commitlint/config-lerna-scopes": "^8.2.0",
|
||||
"@ebay/browserslist-config": "^1.0.1",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-plugin-minprops": "^2.0.1",
|
||||
"benchmark": "^2.1.1",
|
||||
"codecov": "^3.0.2",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"husky": "^4.2.3",
|
||||
"lint-staged": "^7.0.0",
|
||||
"micromatch": "^3.0.4",
|
||||
"mocha": "^5.0.1",
|
||||
"nyc": "^13.0.0",
|
||||
"prettier": "^1.13.5",
|
||||
"shelljs": "^0.7.7",
|
||||
"standard-version": "^7.1.0"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"**/benchmark/**",
|
||||
"**/scripts/**",
|
||||
"**/coverage/**",
|
||||
"**/test/**",
|
||||
"**/test-dist/**",
|
||||
"**/test-generated/**",
|
||||
"**/dist/**"
|
||||
]
|
||||
"chai": "^4.2.0",
|
||||
"codecov": "^3.6.1",
|
||||
"cross-env": "^6.0.3",
|
||||
"dot-json": "^1.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"husky": "^3.1.0",
|
||||
"it-fails": "^1.0.4",
|
||||
"lerna": "^3.19.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-autotest": "^1.0.3",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
||||
|
||||
39
packages/babel-types/package.json
Normal file
39
packages/babel-types/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@marko/babel-types",
|
||||
"description": "Extend babels built in AST with Marko nodes.",
|
||||
"version": "5.0.0",
|
||||
"author": "Dylan Piercey <dpiercey@ebay.com>",
|
||||
"bugs": "https://github.com/marko-js/marko/issues/new?template=Bug_report.md",
|
||||
"peerDependencies": {
|
||||
"@babel/generator": "^7",
|
||||
"@babel/traverse": "^7",
|
||||
"@babel/types": "^7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"self-closing-tags": "^1.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://github.com/marko-js/marko/blob/master/packages/babel-types/README.md",
|
||||
"keywords": [
|
||||
"htmljs",
|
||||
"parser",
|
||||
"babel",
|
||||
"plugin",
|
||||
"parse",
|
||||
"marko"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"main:dev": "src/index.js",
|
||||
"main:npm": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marko-js/marko/tree/master/packages/babel-types"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
114
packages/babel-types/src/definitions/index.js
Normal file
114
packages/babel-types/src/definitions/index.js
Normal file
@ -0,0 +1,114 @@
|
||||
import * as babelTypes from "@babel/types";
|
||||
import { NodePath, Scope } from "@babel/traverse";
|
||||
import builder from "@babel/types/lib/builders/builder";
|
||||
import defineType from "@babel/types/lib/definitions/utils";
|
||||
import * as generatedValidators from "@babel/types/lib/validators/generated";
|
||||
import * as referencedValidators from "@babel/types/lib/validators/isReferenced";
|
||||
import types from "./types";
|
||||
|
||||
const {
|
||||
TYPES,
|
||||
VISITOR_KEYS,
|
||||
FLIPPED_ALIAS_KEYS,
|
||||
DEPRECATED_KEYS,
|
||||
is
|
||||
} = babelTypes;
|
||||
|
||||
const aliases = {};
|
||||
export const MARKO_TYPES = Object.keys(types);
|
||||
MARKO_TYPES.forEach(typeName => {
|
||||
const type = types[typeName];
|
||||
for (const alias of type.aliases) {
|
||||
aliases[alias] = aliases[alias] || [];
|
||||
aliases[alias].push(typeName);
|
||||
}
|
||||
defineType(typeName, type);
|
||||
});
|
||||
const ALIAS_TYPES = Object.keys(aliases);
|
||||
|
||||
// Update TYPES
|
||||
for (const type of [
|
||||
...Object.keys(VISITOR_KEYS),
|
||||
...Object.keys(FLIPPED_ALIAS_KEYS),
|
||||
...Object.keys(DEPRECATED_KEYS)
|
||||
]) {
|
||||
if (!TYPES.includes(type)) TYPES.push(type);
|
||||
}
|
||||
|
||||
// add marko validators & builders to `@babel/types` and `@babel/traverse`
|
||||
MARKO_TYPES.forEach(typeName => {
|
||||
const lowerName = typeName[0].toLowerCase() + typeName.slice(1);
|
||||
const checkKey = `is${typeName}`;
|
||||
const assertKey = `assert${typeName}`;
|
||||
const checkFn = (babelTypes[checkKey] = (node, opts) =>
|
||||
is(typeName, node, opts));
|
||||
const assertFn = (babelTypes[assertKey] = (node, opts) =>
|
||||
assert(typeName, node, opts));
|
||||
NodePath.prototype[checkKey] = function(opts) {
|
||||
return checkFn(this.node, opts);
|
||||
};
|
||||
NodePath.prototype[assertKey] = function(opts) {
|
||||
assertFn(this.node, opts);
|
||||
};
|
||||
// Add builder.
|
||||
babelTypes[typeName] = babelTypes[lowerName] = (...args) =>
|
||||
builder(typeName, ...args);
|
||||
});
|
||||
|
||||
ALIAS_TYPES.forEach(aliasName => {
|
||||
const checkKey = `is${aliasName}`;
|
||||
const originalCheck = generatedValidators[checkKey];
|
||||
generatedValidators[checkKey] = (node, opts) => {
|
||||
return is(aliasName, node, opts) || originalCheck(node, opts);
|
||||
};
|
||||
const originalProtoCheck = NodePath.prototype[checkKey];
|
||||
NodePath.prototype[checkKey] = function(opts) {
|
||||
return (
|
||||
is(aliasName, this.node, opts) ||
|
||||
originalProtoCheck.call(this, this.node, opts)
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const originalIsReferenced = referencedValidators.default;
|
||||
referencedValidators.default = (node, parent, grandparent) => {
|
||||
if (
|
||||
parent.type === "MarkoTag" &&
|
||||
parent.params &&
|
||||
parent.params.includes(node)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return originalIsReferenced(node, parent, grandparent);
|
||||
};
|
||||
|
||||
const originalCrawl = Scope.prototype.crawl;
|
||||
Scope.prototype.crawl = function() {
|
||||
const path = this.path;
|
||||
|
||||
originalCrawl.apply(this, arguments);
|
||||
|
||||
if (path.isMarkoTagBody()) {
|
||||
const params = path.parentPath.get("params");
|
||||
|
||||
if (params.length) {
|
||||
for (const param of params) {
|
||||
this.registerBinding("param", param);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function assert(typeName, node, opts) {
|
||||
if (!is(typeName, node, opts)) {
|
||||
throw new Error(
|
||||
`Expected type "${typeName}" with option ${JSON.stringify(
|
||||
opts
|
||||
)}, but instead got "${node.type}".`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// export babel stuff
|
||||
Object.assign(exports, babelTypes);
|
||||
export * from "@babel/types";
|
||||
206
packages/babel-types/src/definitions/types.js
Normal file
206
packages/babel-types/src/definitions/types.js
Normal file
@ -0,0 +1,206 @@
|
||||
import {
|
||||
assertNodeType,
|
||||
assertValueType,
|
||||
arrayOfType,
|
||||
chain,
|
||||
assertEach
|
||||
} from "@babel/types/lib/definitions/utils";
|
||||
|
||||
import { functionCommon } from "@babel/types/lib/definitions/core";
|
||||
const valueFieldCommon = {
|
||||
value: {
|
||||
validate: assertValueType("string")
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
MarkoDocumentType: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: { ...valueFieldCommon }
|
||||
},
|
||||
|
||||
MarkoDeclaration: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: { ...valueFieldCommon }
|
||||
},
|
||||
|
||||
MarkoCDATA: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: { ...valueFieldCommon }
|
||||
},
|
||||
|
||||
MarkoComment: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: { ...valueFieldCommon }
|
||||
},
|
||||
|
||||
MarkoText: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: { ...valueFieldCommon }
|
||||
},
|
||||
|
||||
MarkoPlaceholder: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["value", "escape"],
|
||||
visitor: ["value"],
|
||||
fields: {
|
||||
value: {
|
||||
validate: assertNodeType("Expression")
|
||||
},
|
||||
escape: {
|
||||
validate: assertValueType("boolean"),
|
||||
default: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoScriptlet: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["body", "static"],
|
||||
visitor: ["body"],
|
||||
fields: {
|
||||
body: {
|
||||
validate: arrayOfType(["Statement"])
|
||||
},
|
||||
static: {
|
||||
validate: assertValueType("boolean"),
|
||||
default: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoClass: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: ["body"],
|
||||
visitor: ["body"],
|
||||
fields: {
|
||||
body: {
|
||||
validate: assertNodeType("ClassBody")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoAttribute: {
|
||||
aliases: ["Marko", "Expression"],
|
||||
builder: ["name", "value", "modifier", "arguments"],
|
||||
visitor: ["value", "arguments"],
|
||||
fields: {
|
||||
name: {
|
||||
validate: assertValueType("string")
|
||||
},
|
||||
value: {
|
||||
validate: assertNodeType("Expression"),
|
||||
optional: true
|
||||
},
|
||||
modifier: {
|
||||
validate: assertValueType("string"),
|
||||
optional: true
|
||||
},
|
||||
arguments: {
|
||||
validate: chain(
|
||||
assertValueType("array"),
|
||||
assertEach(assertNodeType("Expression", "SpreadElement"))
|
||||
),
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoSpreadAttribute: {
|
||||
aliases: ["Marko", "Expression"],
|
||||
builder: ["value"],
|
||||
visitor: ["value"],
|
||||
fields: {
|
||||
value: {
|
||||
validate: assertNodeType("Expression"),
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoTagBody: {
|
||||
aliases: ["Marko", "Block", "BlockParent", "Scope", "Scopable"],
|
||||
builder: ["body"],
|
||||
visitor: ["body"],
|
||||
fields: {
|
||||
body: {
|
||||
validate: arrayOfType([
|
||||
"MarkoTag",
|
||||
"MarkoCDATA",
|
||||
"MarkoText",
|
||||
"MarkoPlaceholder",
|
||||
"MarkoScriptlet",
|
||||
"MarkoComment"
|
||||
]),
|
||||
default: []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
MarkoTag: {
|
||||
aliases: ["Marko", "Statement"],
|
||||
builder: [
|
||||
"name",
|
||||
"attributes",
|
||||
"body",
|
||||
"params",
|
||||
"arguments",
|
||||
"properties",
|
||||
"runtimeFlags"
|
||||
],
|
||||
visitor: ["name", "attributes", "body", "params", "arguments"],
|
||||
fields: {
|
||||
name: {
|
||||
validate: assertNodeType("Expression")
|
||||
},
|
||||
attributes: {
|
||||
validate: arrayOfType(["MarkoAttribute", "MarkoSpreadAttribute"]),
|
||||
default: []
|
||||
},
|
||||
body: {
|
||||
validate: assertNodeType("MarkoTagBody")
|
||||
},
|
||||
params: {
|
||||
...functionCommon.params,
|
||||
optional: true
|
||||
},
|
||||
arguments: {
|
||||
validate: chain(
|
||||
assertValueType("array"),
|
||||
assertEach(assertNodeType("Expression", "SpreadElement"))
|
||||
),
|
||||
optional: true
|
||||
},
|
||||
properties: {
|
||||
validate: arrayOfType(["ObjectProperty"]),
|
||||
default: []
|
||||
},
|
||||
handlers: {
|
||||
validate: assertEach(assertNodeType("Expression")),
|
||||
optional: true
|
||||
},
|
||||
rawValue: {
|
||||
validate: assertValueType("string"),
|
||||
optional: true
|
||||
},
|
||||
runtimeFlags: {
|
||||
validate: assertValueType("number"),
|
||||
default: 0
|
||||
},
|
||||
key: {
|
||||
validate: assertNodeType("Expression"),
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
205
packages/babel-types/src/generators/index.js
Normal file
205
packages/babel-types/src/generators/index.js
Normal file
@ -0,0 +1,205 @@
|
||||
import * as t from "../definitions";
|
||||
import SELF_CLOSING from "self-closing-tags";
|
||||
import Printer from "@babel/generator/lib/printer";
|
||||
|
||||
const UNENCLOSED_WHITESPACE_TYPES = [
|
||||
"LogicalExpression",
|
||||
"AssignmentExpression",
|
||||
"ConditionalExpression",
|
||||
"BinaryExpression",
|
||||
"NewExpression",
|
||||
"Function",
|
||||
"AssignmentExpression"
|
||||
];
|
||||
|
||||
Object.assign(Printer.prototype, {
|
||||
MarkoDocumentType(node) {
|
||||
this.token("<!");
|
||||
this.token(node.value);
|
||||
this.token(">");
|
||||
},
|
||||
MarkoDeclaration(node) {
|
||||
this.token("<?");
|
||||
this.token(node.value);
|
||||
this.token("?>");
|
||||
},
|
||||
MarkoCDATA(node) {
|
||||
this.token("<![CDATA[");
|
||||
this.token(node.value);
|
||||
this.token("]]>");
|
||||
},
|
||||
MarkoComment(node) {
|
||||
this.token("<!--");
|
||||
this.token(node.value);
|
||||
this.token("-->");
|
||||
},
|
||||
MarkoPlaceholder(node, parent) {
|
||||
const parentBody = parent.body;
|
||||
const prev = parentBody[parentBody.indexOf(node) - 1];
|
||||
|
||||
if (prev && (t.isMarkoText(prev) || t.isMarkoPlaceholder(prev))) {
|
||||
this.removeTrailingNewline();
|
||||
}
|
||||
|
||||
this.token(node.escape ? "${" : "$!{");
|
||||
this.print(node.value, node);
|
||||
this.token("}");
|
||||
},
|
||||
MarkoScriptlet(node, parent) {
|
||||
this.removeTrailingNewline();
|
||||
|
||||
if (!(t.isProgram(parent) && parent.body.indexOf(node) === 0)) {
|
||||
this.token("\n");
|
||||
}
|
||||
|
||||
this.token(`${node.static ? "static" : "$"} `);
|
||||
|
||||
if (node.body.length === 1) {
|
||||
// TODO should determine if node has unenclosed newlines.
|
||||
this.print(node.body[0], node);
|
||||
} else {
|
||||
this.token("{");
|
||||
this.newline();
|
||||
this.indent();
|
||||
this.printSequence(node.body, node);
|
||||
this.dedent();
|
||||
this.token("}");
|
||||
}
|
||||
},
|
||||
MarkoClass(node) {
|
||||
this.token("class");
|
||||
this.token(" ");
|
||||
this.print(node.body, node);
|
||||
},
|
||||
MarkoAttribute(node) {
|
||||
this.token(node.name);
|
||||
|
||||
if (node.modifier) {
|
||||
this.token(":");
|
||||
this.token(node.modifier);
|
||||
}
|
||||
|
||||
if (node.arguments && node.arguments.length) {
|
||||
this.token("(");
|
||||
this.printList(node.arguments, node);
|
||||
this.token(")");
|
||||
}
|
||||
|
||||
if (!t.isBooleanLiteral(node.value) || !node.value.value) {
|
||||
this.token("=");
|
||||
printWithParansIfNeeded.call(this, node.value, node);
|
||||
}
|
||||
},
|
||||
MarkoSpreadAttribute(node) {
|
||||
this.token("...");
|
||||
printWithParansIfNeeded.call(this, node.value, node);
|
||||
},
|
||||
MarkoText(node, parent) {
|
||||
const parentBody = parent.body;
|
||||
const prev = parentBody[parentBody.indexOf(node) - 1];
|
||||
const concatToPrev = prev && t.isMarkoPlaceholder(prev);
|
||||
let { value } = node;
|
||||
|
||||
if (concatToPrev) {
|
||||
this.removeTrailingNewline();
|
||||
}
|
||||
|
||||
const isMultiLine = /[\r\n]/g.test(value);
|
||||
const isRootLevel = !concatToPrev && t.isProgram(parent);
|
||||
|
||||
if (isRootLevel) {
|
||||
if (isMultiLine) {
|
||||
this.token("---\n");
|
||||
} else {
|
||||
this.token("-- ");
|
||||
}
|
||||
}
|
||||
|
||||
this.word(value);
|
||||
|
||||
if (isMultiLine && isRootLevel) {
|
||||
this.token("\n---");
|
||||
}
|
||||
},
|
||||
MarkoTag(node) {
|
||||
const isDynamicTag = !t.isStringLiteral(node.name);
|
||||
const tagName = !isDynamicTag && node.name.value;
|
||||
const selfClosing =
|
||||
!node.body.body.length || SELF_CLOSING.includes(tagName);
|
||||
const rawValue = node.rawValue;
|
||||
|
||||
if (
|
||||
tagName === "style" &&
|
||||
/^style(?:\.[^\s]+)?\s*\{[\s\S]*}$/.test(rawValue)
|
||||
) {
|
||||
this.token(rawValue);
|
||||
return;
|
||||
}
|
||||
|
||||
this.token("<");
|
||||
|
||||
if (rawValue) {
|
||||
this.token(rawValue);
|
||||
} else {
|
||||
if (isDynamicTag) {
|
||||
this.token("${");
|
||||
this.print(node.name, node);
|
||||
this.token("}");
|
||||
} else {
|
||||
this.token(tagName);
|
||||
}
|
||||
|
||||
if (node.arguments && node.arguments.length) {
|
||||
this.token("(");
|
||||
this.printList(node.arguments, node);
|
||||
this.token(")");
|
||||
}
|
||||
|
||||
if (node.params && node.params.length) {
|
||||
this.token("|");
|
||||
this.printList(node.params, node);
|
||||
this.token("|");
|
||||
}
|
||||
|
||||
if (node.attributes.length) {
|
||||
this.token(" ");
|
||||
this.printJoin(node.attributes, node, { separator: spaceSeparator });
|
||||
}
|
||||
}
|
||||
|
||||
if (selfClosing) {
|
||||
this.token("/>");
|
||||
} else {
|
||||
this.token(">");
|
||||
this.newline();
|
||||
this.printSequence(node.body.body, node.body, { indent: true });
|
||||
this.token("</");
|
||||
if (!isDynamicTag) {
|
||||
this.token(tagName);
|
||||
}
|
||||
this.token(">");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function spaceSeparator() {
|
||||
this.token(" ");
|
||||
}
|
||||
|
||||
function printWithParansIfNeeded(value, parent) {
|
||||
const needsParans = hasUnenclosedWhitespace(value);
|
||||
|
||||
if (needsParans) {
|
||||
this.token("(");
|
||||
}
|
||||
|
||||
this.print(value, parent);
|
||||
|
||||
if (needsParans) {
|
||||
this.token(")");
|
||||
}
|
||||
}
|
||||
|
||||
function hasUnenclosedWhitespace(node) {
|
||||
return UNENCLOSED_WHITESPACE_TYPES.includes(node.type);
|
||||
}
|
||||
3
packages/babel-types/src/index.js
Normal file
3
packages/babel-types/src/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import "./generators";
|
||||
import * as types from "./definitions";
|
||||
export { types };
|
||||
35
packages/babel-utils/package.json
Normal file
35
packages/babel-utils/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@marko/babel-utils",
|
||||
"description": "Utilities for use with Marko babel plugins.",
|
||||
"version": "5.0.0",
|
||||
"author": "Dylan Piercey <dpiercey@ebay.com>",
|
||||
"bugs": "https://github.com/marko-js/marko/issues/new?template=Bug_report.md",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@marko/babel-types": "^5.0.0",
|
||||
"jsesc": "^2.5.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://github.com/marko-js/marko/blob/master/packages/babel-utils/README.md",
|
||||
"keywords": [
|
||||
"htmljs",
|
||||
"parser",
|
||||
"babel",
|
||||
"plugin",
|
||||
"parse",
|
||||
"marko"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"main:dev": "src/index.js",
|
||||
"main:npm": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marko-js/marko/tree/master/packages/babel-utils"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
45
packages/babel-utils/src/assert.js
Normal file
45
packages/babel-utils/src/assert.js
Normal file
@ -0,0 +1,45 @@
|
||||
export function assertAllowedAttributes(path, allowed) {
|
||||
const { node } = path;
|
||||
node.attributes.forEach((attr, i) => {
|
||||
if (!allowed.includes(attr.name)) {
|
||||
throw path
|
||||
.get(`attributes.${i}`)
|
||||
.buildCodeFrameError(
|
||||
`Invalid "${node.name.value}" tag attribute: "${attr.name}".`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function assertNoAttributes(path) {
|
||||
assertAllowedAttributes(path, []);
|
||||
}
|
||||
|
||||
export function assertNoParams(path) {
|
||||
const { hub } = path;
|
||||
const params = path.get("params");
|
||||
if (params.length) {
|
||||
const start = params[0].node.start;
|
||||
const end = params[params.length - 1].node.end;
|
||||
throw hub.buildError({ start, end }, "Tag does not support parameters.");
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNoAttributeTags(path) {
|
||||
const exampleAttributeTag = path.get("exampleAttributeTag");
|
||||
if (exampleAttributeTag.node) {
|
||||
throw exampleAttributeTag
|
||||
.get("name")
|
||||
.buildCodeFrameError("@tags must be within a custom element.");
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNoArgs(path) {
|
||||
const { hub } = path;
|
||||
const args = path.get("arguments");
|
||||
if (args.length) {
|
||||
const start = args[0].node.start;
|
||||
const end = args[args.length - 1].node.end;
|
||||
throw hub.buildError({ start, end }, "Tag does not support arguments.");
|
||||
}
|
||||
}
|
||||
21
packages/babel-utils/src/index.js
Normal file
21
packages/babel-utils/src/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
export {
|
||||
isNativeTag,
|
||||
isMacroTag,
|
||||
isDynamicTag,
|
||||
isAttributeTag,
|
||||
isTransparentTag,
|
||||
getMacroIdentifier,
|
||||
getTagDef,
|
||||
getFullyResolvedTagName,
|
||||
findParentTag,
|
||||
findAttributeTags,
|
||||
getArgOrSequence
|
||||
} from "./tags";
|
||||
export {
|
||||
assertAllowedAttributes,
|
||||
assertNoArgs,
|
||||
assertNoAttributes,
|
||||
assertNoParams,
|
||||
assertNoAttributeTags
|
||||
} from "./assert";
|
||||
export { normalizeTemplateString } from "./template-string";
|
||||
123
packages/babel-utils/src/tags.js
Normal file
123
packages/babel-utils/src/tags.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
const transparentTags = new Set(["for", "while", "if", "else", "_no-update"]);
|
||||
|
||||
export function isNativeTag(path) {
|
||||
const tagDef = path.node.tagDef;
|
||||
return (
|
||||
tagDef &&
|
||||
tagDef.html &&
|
||||
(tagDef.htmlType === "custom-element" ||
|
||||
(!tagDef.template && !tagDef.renderer))
|
||||
);
|
||||
}
|
||||
|
||||
export function isDynamicTag(path) {
|
||||
return !path.get("name").isStringLiteral();
|
||||
}
|
||||
|
||||
export function isAttributeTag(path) {
|
||||
return !isDynamicTag(path) && path.get("name.value").node[0] === "@";
|
||||
}
|
||||
|
||||
export function isTransparentTag(path) {
|
||||
return (
|
||||
!isDynamicTag(path) && transparentTags.has(path.get("name.value").node)
|
||||
);
|
||||
}
|
||||
|
||||
export function isMacroTag(path) {
|
||||
return Boolean(getMacroIdentifier(path));
|
||||
}
|
||||
|
||||
export function getMacroIdentifier(path) {
|
||||
return !isDynamicTag(path) && path.hub.macros[path.get("name.value").node];
|
||||
}
|
||||
|
||||
export function getTagDef(path) {
|
||||
const cached = path.get("tagDef");
|
||||
|
||||
if (cached.node !== undefined) {
|
||||
return cached.node;
|
||||
}
|
||||
|
||||
const { hub } = path;
|
||||
const { lookup } = hub;
|
||||
let tagName;
|
||||
|
||||
if (!(isMacroTag(path) || isDynamicTag(path))) {
|
||||
tagName = isAttributeTag(path)
|
||||
? getFullyResolvedTagName(path)
|
||||
: path.get("name.value").node;
|
||||
}
|
||||
|
||||
const tagDef = (tagName && lookup.getTag(tagName)) || null;
|
||||
path.set("tagDef", tagDef);
|
||||
return tagDef;
|
||||
}
|
||||
|
||||
export function getFullyResolvedTagName(path) {
|
||||
const parts = [];
|
||||
let cur;
|
||||
do {
|
||||
cur = path.node.name.value;
|
||||
|
||||
if (isAttributeTag(path)) {
|
||||
parts.push(cur.slice(1));
|
||||
} else {
|
||||
parts.push(cur || "*");
|
||||
break;
|
||||
}
|
||||
} while ((path = findParentTag(path)));
|
||||
|
||||
return parts.reverse().join(":");
|
||||
}
|
||||
|
||||
export function findParentTag(path) {
|
||||
let cur = path.parentPath;
|
||||
|
||||
while (cur.node) {
|
||||
if (cur.isMarkoTagBody()) {
|
||||
cur = cur.parentPath;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cur.isMarkoTag()) {
|
||||
cur = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isTransparentTag(cur)) {
|
||||
cur = cur.parentPath;
|
||||
continue;
|
||||
}
|
||||
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
export function findAttributeTags(path, attributeTags = []) {
|
||||
path.get("body.body").forEach(child => {
|
||||
if (isAttributeTag(child)) {
|
||||
attributeTags.push(child);
|
||||
} else if (isTransparentTag(child)) {
|
||||
findAttributeTags(child, attributeTags);
|
||||
}
|
||||
});
|
||||
|
||||
return attributeTags;
|
||||
}
|
||||
|
||||
export function getArgOrSequence(path) {
|
||||
const {
|
||||
node: { arguments: args }
|
||||
} = path;
|
||||
const len = args && args.length;
|
||||
|
||||
if (len) {
|
||||
if (len > 1) {
|
||||
return t.sequenceExpression(args);
|
||||
} else {
|
||||
return args[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
56
packages/babel-utils/src/template-string.js
Normal file
56
packages/babel-utils/src/template-string.js
Normal file
@ -0,0 +1,56 @@
|
||||
import jsesc from "jsesc";
|
||||
import { types as t } from "@marko/babel-types";
|
||||
|
||||
export function normalizeTemplateString(quasis, ...expressions) {
|
||||
quasis = quasis.map(q => (t.isTemplateElement(q) ? q.value.cooked : q));
|
||||
|
||||
for (let i = expressions.length; i--; ) {
|
||||
let v = expressions[i];
|
||||
if (t.isTemplateLiteral(v)) {
|
||||
quasis[i] += v.quasis[0].value.cooked;
|
||||
quasis[i + 1] =
|
||||
v.quasis[v.quasis.length - 1].value.cooked + (quasis[i + 1] || "");
|
||||
quasis.splice(
|
||||
i + 1,
|
||||
0,
|
||||
...v.quasis.slice(1, -1).map(q => q.value.cooked)
|
||||
);
|
||||
expressions.splice(i, 1, ...v.expressions);
|
||||
i += v.expressions.length;
|
||||
} else if (t.isStringLiteral(v) || typeof v === "string") {
|
||||
const value = t.isStringLiteral(v) ? v.value : v;
|
||||
quasis[i] += value + quasis[i + 1];
|
||||
expressions.splice(i, 1);
|
||||
quasis.splice(i + 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!expressions.length) {
|
||||
// No expression, just return a literal or empty.
|
||||
const literal = quasis.join("");
|
||||
return literal === "" ? undefined : t.stringLiteral(literal);
|
||||
}
|
||||
|
||||
if (
|
||||
expressions.length === 1 &&
|
||||
quasis.length === 2 &&
|
||||
quasis.every(isEmptyString)
|
||||
) {
|
||||
// Only expression `${expr}` just return the expr.
|
||||
return expressions[0];
|
||||
}
|
||||
|
||||
// Do it.
|
||||
return t.templateLiteral(quasis.map(getTemplateElement), expressions);
|
||||
}
|
||||
|
||||
function getTemplateElement(s = "") {
|
||||
return t.templateElement({
|
||||
cooked: s,
|
||||
raw: jsesc(s, { quotes: "backtick" })
|
||||
});
|
||||
}
|
||||
|
||||
function isEmptyString(s = "") {
|
||||
return s === "";
|
||||
}
|
||||
51
packages/compiler/package.json
Normal file
51
packages/compiler/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@marko/compiler",
|
||||
"description": "Marko template to JS compiler.",
|
||||
"version": "5.0.0",
|
||||
"author": "Dylan Piercey <dpiercey@ebay.com>",
|
||||
"bugs": "https://github.com/marko-js/marko/issues/new?template=Bug_report.md",
|
||||
"dependencies": {
|
||||
"@marko/babel-types": "^5.0.0",
|
||||
"@marko/babel-utils": "^5.0.0",
|
||||
"@marko/translator-default": "^5.0.0",
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@babel/traverse": "^7.7.4",
|
||||
"@babel/types": "^7.7.4",
|
||||
"@babel/parser": "^7.7.7",
|
||||
"@babel/generator": "^7.7.7",
|
||||
"complain": "^1.6.0",
|
||||
"he": "^1.1.0",
|
||||
"htmljs-parser": "^2.7.1",
|
||||
"jsesc": "^2.5.2",
|
||||
"lasso-modules-client": "^2.0.4",
|
||||
"marko": "^5.0.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"self-closing-tags": "^1.0.1",
|
||||
"strip-ansi": "^5.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://github.com/marko-js/marko/blob/master/packages/compiler/README.md",
|
||||
"keywords": [
|
||||
"htmljs",
|
||||
"parser",
|
||||
"babel",
|
||||
"plugin",
|
||||
"parse",
|
||||
"marko"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"main:dev": "src/index.js",
|
||||
"main:npm": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marko-js/marko/tree/master/packages/compiler"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
172
packages/compiler/src/babel-plugin/hub.js
Normal file
172
packages/compiler/src/babel-plugin/hub.js
Normal file
@ -0,0 +1,172 @@
|
||||
import path from "path";
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import { getClientPath } from "lasso-modules-client/transport";
|
||||
import { parse, parseExpression } from "@babel/parser";
|
||||
import createFile from "./util/create-file";
|
||||
import codeFrameError from "./util/code-frame-error";
|
||||
import codeFrameWarning from "./util/code-frame-warning";
|
||||
import { getLocRange } from "./util/get-loc";
|
||||
import checksum from "./util/checksum";
|
||||
export class Hub {
|
||||
constructor(filename, code, options) {
|
||||
this._code = code;
|
||||
this.options = options;
|
||||
this.filename = filename;
|
||||
this.file = createFile(filename, code);
|
||||
this.lookup = this.options.lookup;
|
||||
this.macros = Object.create(null);
|
||||
this.meta = this.file.markoMeta = {
|
||||
id: checksum(this.getClientPath(this.filename)),
|
||||
deps: [],
|
||||
tags: []
|
||||
};
|
||||
this._imports = Object.create(null);
|
||||
}
|
||||
|
||||
getCode() {
|
||||
return this._code;
|
||||
}
|
||||
|
||||
getWhitespaceBefore(pos) {
|
||||
return (
|
||||
this._codeAsWhitespace ||
|
||||
(this._codeAsWhitespace = this._code.replace(/[^\n\r ]/g, " "))
|
||||
).slice(0, pos);
|
||||
}
|
||||
|
||||
buildError(node, msg) {
|
||||
return codeFrameError(this.filename, this._code, msg, node.start, node.end);
|
||||
}
|
||||
|
||||
buildWarning(node, msg) {
|
||||
return codeFrameWarning(this.filename, this._code, msg, node);
|
||||
}
|
||||
|
||||
getClientPath(file) {
|
||||
return getClientPath(file);
|
||||
}
|
||||
|
||||
resolveRelativePath(filename) {
|
||||
const dir = path.dirname(this.filename);
|
||||
let relativePath = path.isAbsolute(filename)
|
||||
? path.relative(dir, filename)
|
||||
: filename;
|
||||
if (/^[^./]/.test(relativePath)) relativePath = `./${relativePath}`;
|
||||
return relativePath.replace(/^(?:\.{1,2}\/)+node_modules\//, "");
|
||||
}
|
||||
|
||||
importDefault(path, file, nameHint) {
|
||||
file = remapProductionMarkoBuild(path, file);
|
||||
const { _imports } = this;
|
||||
let importDeclaration = _imports[file];
|
||||
|
||||
if (!importDeclaration) {
|
||||
importDeclaration = _imports[file] = this.program.pushContainer(
|
||||
"body",
|
||||
t.importDeclaration([], t.stringLiteral(file))
|
||||
)[0];
|
||||
}
|
||||
|
||||
if (!nameHint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const specifiers = importDeclaration.get("specifiers");
|
||||
const specifier = specifiers.find(specifier =>
|
||||
specifier.isImportDefaultSpecifier()
|
||||
);
|
||||
|
||||
if (!specifier) {
|
||||
const identifier = path.scope.generateUidIdentifier(nameHint);
|
||||
importDeclaration.pushContainer(
|
||||
"specifiers",
|
||||
t.importDefaultSpecifier(identifier)
|
||||
);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
return t.identifier(specifier.node.local.name);
|
||||
}
|
||||
|
||||
importNamed(path, file, name, nameHint = name) {
|
||||
file = remapProductionMarkoBuild(path, file);
|
||||
const { _imports } = this;
|
||||
let importDeclaration = _imports[file];
|
||||
|
||||
if (!importDeclaration) {
|
||||
importDeclaration = _imports[file] = this.program.pushContainer(
|
||||
"body",
|
||||
t.importDeclaration([], t.stringLiteral(file))
|
||||
)[0];
|
||||
}
|
||||
|
||||
const specifiers = importDeclaration.get("specifiers");
|
||||
const specifier = specifiers.find(
|
||||
specifier =>
|
||||
specifier.isImportSpecifier() && specifier.node.imported.name === name
|
||||
);
|
||||
|
||||
if (!specifier) {
|
||||
const identifier = path.scope.generateUidIdentifier(nameHint);
|
||||
importDeclaration.pushContainer(
|
||||
"specifiers",
|
||||
t.importSpecifier(identifier, t.identifier(name))
|
||||
);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
return t.identifier(specifier.node.local.name);
|
||||
}
|
||||
|
||||
addStaticNode(node) {
|
||||
this.program.pushContainer("body", node);
|
||||
}
|
||||
|
||||
createNode(type, start, end, ...args) {
|
||||
return {
|
||||
...t[type](...args),
|
||||
...getLocRange(this._code, start, end)
|
||||
};
|
||||
}
|
||||
|
||||
parse(str, start) {
|
||||
return this._tryParseJS(false, str, start);
|
||||
}
|
||||
|
||||
parseExpression(str, start) {
|
||||
return this._tryParseJS(true, str, start);
|
||||
}
|
||||
|
||||
_tryParseJS(isExpression, str, start) {
|
||||
const opts = this.options.jsParseOptions;
|
||||
str = this.getWhitespaceBefore(start) + str;
|
||||
|
||||
try {
|
||||
return isExpression
|
||||
? parseExpression(str, opts)
|
||||
: parse(str, opts).program;
|
||||
} catch (err) {
|
||||
let { pos, message } = err;
|
||||
if (pos) {
|
||||
throw codeFrameError(
|
||||
this.filename,
|
||||
this._code,
|
||||
message.replace(/ *\(\d+:\d+\)$/, ""),
|
||||
pos
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function remapProductionMarkoBuild(path, file) {
|
||||
const {
|
||||
hub: {
|
||||
options: { isProduction }
|
||||
}
|
||||
} = path;
|
||||
if (!isProduction) return file;
|
||||
return file.replace(/^marko\/src\//, "marko/dist/");
|
||||
}
|
||||
77
packages/compiler/src/babel-plugin/index.js
Normal file
77
packages/compiler/src/babel-plugin/index.js
Normal file
@ -0,0 +1,77 @@
|
||||
import { extname, dirname } from "path";
|
||||
import { Hub } from "./hub";
|
||||
import { parse } from "./parser";
|
||||
import { visitor as migrate } from "./plugins/migrate";
|
||||
import { visitor as transform } from "./plugins/transform";
|
||||
import { NodePath, visitors } from "@babel/traverse";
|
||||
import { buildLookup } from "../taglib";
|
||||
|
||||
export default (api, options) => {
|
||||
api.assertVersion(7);
|
||||
options.output = options.output || "html";
|
||||
options.translator = options.translator || "default";
|
||||
|
||||
const isProduction = api.env("production");
|
||||
const translator = require(`@marko/translator-${options.translator}`);
|
||||
|
||||
return {
|
||||
name: "marko",
|
||||
parserOverride(code, jsParseOptions) {
|
||||
const filename = jsParseOptions.sourceFileName;
|
||||
const hub = new Hub(filename, code, {
|
||||
...options,
|
||||
jsParseOptions,
|
||||
isProduction,
|
||||
lookup: buildLookup(dirname(filename), translator.taglibs)
|
||||
});
|
||||
|
||||
// Only run on Marko files.
|
||||
if (!(extname(filename) === ".marko" || options.allExtensions)) {
|
||||
return hub.parse(code, 0);
|
||||
}
|
||||
|
||||
const nodePath = new NodePath(hub);
|
||||
nodePath.node = hub.file;
|
||||
hub.program = nodePath.get("program");
|
||||
parse(nodePath);
|
||||
|
||||
// TODO: this package should be split into 4:
|
||||
// 1. babel-syntax-marko (removes the need for the _parseOnly option)
|
||||
// 2. babel-plugin-migrate-marko (removes the need for the _migrateOnly option)
|
||||
// 3. babel-plugin-transform-marko (only runs transformers without converting Marko nodes to js)
|
||||
// 4. babel-plugin-translate-marko (runs final translations)
|
||||
if (!options._parseOnly) {
|
||||
nodePath.get("program").scope.crawl(); // Initialize bindings.
|
||||
const rootMigrators = Object.values(hub.lookup.taglibsById)
|
||||
.map(taglib => taglib.getMigrator())
|
||||
.filter(m => m)
|
||||
.map(m => m.default || m)
|
||||
.map(m => m(api, options));
|
||||
nodePath.traverse(
|
||||
rootMigrators.length
|
||||
? visitors.merge(rootMigrators.concat(migrate))
|
||||
: migrate
|
||||
);
|
||||
if (!options._migrateOnly) {
|
||||
const rootTransformers = hub.lookup.merged.transformers
|
||||
.map(t => require(t.path))
|
||||
.map(t => t.default || t)
|
||||
.map(t => t(api, options));
|
||||
nodePath.traverse(
|
||||
rootTransformers.length
|
||||
? visitors.merge(rootTransformers.concat(transform))
|
||||
: transform
|
||||
);
|
||||
|
||||
nodePath.traverse(translator.visitor);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign({}, hub.file);
|
||||
},
|
||||
post(file) {
|
||||
// Attach marko metadata to babel metadata.
|
||||
file.metadata.marko = file.ast.markoMeta;
|
||||
}
|
||||
};
|
||||
};
|
||||
322
packages/compiler/src/babel-plugin/parser.js
Normal file
322
packages/compiler/src/babel-plugin/parser.js
Normal file
@ -0,0 +1,322 @@
|
||||
import { createParser } from "htmljs-parser";
|
||||
import parseAttributes from "./util/parse-attributes";
|
||||
import parseArguments from "./util/parse-arguments";
|
||||
import parseParams from "./util/parse-params";
|
||||
import parseIDShorthand from "./util/parse-id-shorthand";
|
||||
import parseClassnameShorthand from "./util/parse-classname-shorthand";
|
||||
import { getLocRange } from "./util/get-loc";
|
||||
import { types as t } from "@marko/babel-types";
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
const EMPTY_ARRAY = [];
|
||||
const htmlTrimStart = t => t.replace(/^[\n\r]\s*/, "");
|
||||
const htmlTrimEnd = t => t.replace(/[\n\r]\s*$/, "");
|
||||
const htmlTrim = t => htmlTrimStart(htmlTrimEnd(t));
|
||||
const isAttributeTag = node =>
|
||||
t.isStringLiteral(node.name) && node.name.value[0] === "@";
|
||||
|
||||
export function parse(fileNodePath) {
|
||||
const { hub } = fileNodePath;
|
||||
const { filename, htmlParseOptions = {} } = hub;
|
||||
const { preserveWhitespace } = htmlParseOptions;
|
||||
const code = hub.getCode();
|
||||
const getTagBody = () =>
|
||||
currentTag.get(currentTag.isFile() ? "program" : "body");
|
||||
const pushTagBody = node => getTagBody().pushContainer("body", node);
|
||||
let currentTag = fileNodePath;
|
||||
let preservingWhitespaceUntil = preserveWhitespace;
|
||||
let wasSelfClosing = false;
|
||||
let handledTagName = false;
|
||||
let onNext;
|
||||
|
||||
const handlers = {
|
||||
onDocumentType({ value, pos, endPos }) {
|
||||
const node = hub.createNode("markoDocumentType", pos, endPos, value);
|
||||
pushTagBody(node);
|
||||
/* istanbul ignore next */
|
||||
onNext = onNext && onNext(node);
|
||||
},
|
||||
|
||||
onDeclaration({ value, pos, endPos }) {
|
||||
const node = hub.createNode("markoDeclaration", pos, endPos, value);
|
||||
pushTagBody(node);
|
||||
/* istanbul ignore next */
|
||||
onNext = onNext && onNext(node);
|
||||
},
|
||||
|
||||
onComment({ value, pos, endPos }) {
|
||||
const node = hub.createNode("markoComment", pos, endPos, value);
|
||||
pushTagBody(node);
|
||||
onNext = onNext && onNext(node);
|
||||
},
|
||||
|
||||
onCDATA({ value, pos, endPos }) {
|
||||
const node = hub.createNode("markoCDATA", pos, endPos, value);
|
||||
pushTagBody(node);
|
||||
onNext = onNext && onNext(node);
|
||||
},
|
||||
|
||||
onText({ value }, { pos }) {
|
||||
const shouldTrim = !preservingWhitespaceUntil;
|
||||
const { body } = getTagBody().node;
|
||||
|
||||
if (shouldTrim) {
|
||||
if (htmlTrim(value) === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find previous non-scriptlet/@tag.
|
||||
let prev;
|
||||
let prevIndex = body.length;
|
||||
while (prevIndex > 0) {
|
||||
prev = body[--prevIndex];
|
||||
|
||||
if (
|
||||
t.isMarkoClass(prev) ||
|
||||
t.isMarkoComment(prev) ||
|
||||
t.isMarkoScriptlet(prev) ||
|
||||
isAttributeTag(prev)
|
||||
) {
|
||||
prev = undefined;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prev) {
|
||||
const originalValue = value;
|
||||
value = htmlTrimStart(value);
|
||||
pos += originalValue.indexOf(value);
|
||||
} else if (
|
||||
t.isMarkoText(prev) &&
|
||||
/\s/.test(prev.value[prev.value.length - 1])
|
||||
) {
|
||||
const originalValue = value;
|
||||
value = value.replace(/^\s+/, "");
|
||||
pos += originalValue.indexOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
const endPos = pos + value.length;
|
||||
const node = hub.createNode("markoText", pos, endPos, value);
|
||||
const prevBody = getTagBody().node.body;
|
||||
pushTagBody(node);
|
||||
onNext && onNext(node);
|
||||
onNext =
|
||||
shouldTrim &&
|
||||
(next => {
|
||||
if (!next || prevBody.indexOf(next) === -1) {
|
||||
node.value = htmlTrimEnd(node.value);
|
||||
}
|
||||
|
||||
node.value = node.value.replace(/\s+/g, " ");
|
||||
});
|
||||
},
|
||||
|
||||
onPlaceholder({ escape, value, withinBody, pos, endPos }) {
|
||||
if (withinBody) {
|
||||
const node = hub.createNode(
|
||||
"markoPlaceholder",
|
||||
pos,
|
||||
endPos,
|
||||
hub.parseExpression(value, pos + (escape ? 2 /* ${ */ : 3) /* $!{ */),
|
||||
escape
|
||||
);
|
||||
|
||||
pushTagBody(node);
|
||||
onNext = onNext && onNext(node);
|
||||
}
|
||||
},
|
||||
|
||||
onScriptlet({ value, line, block, pos, endPos }) {
|
||||
if (!line && !block) {
|
||||
throw hub.buildError(
|
||||
{ start: pos, end: endPos },
|
||||
"<% scriptlets %> are no longer supported."
|
||||
);
|
||||
}
|
||||
|
||||
pos -= 1; // Include $.
|
||||
// Scriptlets are ignored as content and don't call `onNext`.
|
||||
pushTagBody(
|
||||
hub.createNode(
|
||||
"markoScriptlet",
|
||||
pos,
|
||||
endPos,
|
||||
hub.parse(value, pos + 2 /** Ignores leading `$ ` */).body
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
onOpenTagName(event) {
|
||||
const { pos, endPos } = event;
|
||||
const tagName = event.tagName || "div";
|
||||
const [, tagNameExpression] =
|
||||
/^\$\{([\s\S]*)\}/.exec(tagName) || EMPTY_ARRAY;
|
||||
const tagDef = !tagNameExpression && hub.lookup.getTag(tagName);
|
||||
const tagNameStartPos = pos + (event.concise ? 0 : 1); // Account for leading `<`.
|
||||
|
||||
handledTagName = true;
|
||||
|
||||
if (tagNameExpression === "") {
|
||||
throw hub.buildError(
|
||||
{ start: tagNameStartPos + 1, end: tagNameStartPos + 3 },
|
||||
"Missing expression for <${dynamic}> tag."
|
||||
);
|
||||
}
|
||||
|
||||
const node = hub.createNode(
|
||||
"markoTag",
|
||||
pos,
|
||||
endPos,
|
||||
tagNameExpression
|
||||
? hub.parseExpression(tagNameExpression, tagNameStartPos + 2 /* ${ */)
|
||||
: hub.createNode(
|
||||
"stringLiteral",
|
||||
tagNameStartPos,
|
||||
tagNameStartPos + tagName.length,
|
||||
tagName
|
||||
),
|
||||
[],
|
||||
t.markoTagBody()
|
||||
);
|
||||
|
||||
if (tagDef) {
|
||||
node.tagDef = tagDef;
|
||||
|
||||
const { parseOptions } = tagDef;
|
||||
if (parseOptions) {
|
||||
event.setParseOptions(parseOptions);
|
||||
|
||||
if (parseOptions.rootOnly && !currentTag.isFile()) {
|
||||
throw hub.buildError(
|
||||
{ start: pos, end: endPos },
|
||||
`"${tagName}" tags must be at the root of your Marko template.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[currentTag] = pushTagBody(node);
|
||||
|
||||
// @tags are not treated as content and do not call next.
|
||||
if (!isAttributeTag(node)) {
|
||||
onNext = onNext && onNext(node);
|
||||
}
|
||||
},
|
||||
|
||||
onOpenTag(event, parser) {
|
||||
if (!handledTagName) {
|
||||
// There is a bug in htmljs parser where a single top level concise mode tag with nothing else
|
||||
// does not emit the openTagNameEvent.
|
||||
handlers.onOpenTagName(event);
|
||||
}
|
||||
|
||||
handledTagName = false;
|
||||
const { pos, endPos, tagNameEndPos } = event;
|
||||
const { tagDef } = currentTag.node;
|
||||
const parseOptions = (tagDef && tagDef.parseOptions) || EMPTY_OBJECT;
|
||||
wasSelfClosing = event.selfClosed;
|
||||
|
||||
if (parseOptions.state === "parsed-text") {
|
||||
parser.enterParsedTextContentState();
|
||||
} else if (parseOptions.state === "static-text") {
|
||||
parser.enterStaticTextContentState();
|
||||
}
|
||||
|
||||
if (parseOptions.rawOpenTag) {
|
||||
currentTag.set(
|
||||
"rawValue",
|
||||
parser.substring(pos, endPos).replace(/^<|\/>$|>$/g, "")
|
||||
);
|
||||
}
|
||||
|
||||
if (!parseOptions.ignoreAttributes) {
|
||||
currentTag.set("params", parseParams(hub, event.params));
|
||||
currentTag.set("arguments", parseArguments(hub, event.argument));
|
||||
currentTag.set(
|
||||
"attributes",
|
||||
parseIDShorthand(
|
||||
hub,
|
||||
event.shorthandId,
|
||||
parseClassnameShorthand(
|
||||
hub,
|
||||
event.shorthandClassNames,
|
||||
parseAttributes(hub, event.attributes, tagNameEndPos)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!preservingWhitespaceUntil && parseOptions.preserveWhitespace) {
|
||||
preservingWhitespaceUntil = currentTag;
|
||||
}
|
||||
},
|
||||
|
||||
onCloseTag(event, parser) {
|
||||
let { pos, endPos } = event;
|
||||
const tag = currentTag;
|
||||
const { node } = tag;
|
||||
const { tagDef } = node;
|
||||
const isConcise = code[pos] !== "<";
|
||||
|
||||
if (preservingWhitespaceUntil === currentTag) {
|
||||
preservingWhitespaceUntil = undefined;
|
||||
}
|
||||
|
||||
if (!pos) {
|
||||
pos = parser.pos;
|
||||
}
|
||||
|
||||
if (!endPos) {
|
||||
endPos = pos;
|
||||
|
||||
if (wasSelfClosing && !isConcise) {
|
||||
endPos += 2; // account for "/>"
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(node, getLocRange(code, node.start, endPos));
|
||||
|
||||
if (
|
||||
!isConcise &&
|
||||
!wasSelfClosing &&
|
||||
code[pos + 1] !== "/" &&
|
||||
!currentTag.get("name").isStringLiteral()
|
||||
) {
|
||||
throw hub.buildError(
|
||||
{ start: pos, end: endPos },
|
||||
`Invalid ending for dynamic tag, expected "</>".`
|
||||
);
|
||||
}
|
||||
|
||||
if (tagDef && tagDef.nodeFactoryPath) {
|
||||
const module = require(tagDef.nodeFactoryPath);
|
||||
/* istanbul ignore next */
|
||||
const { default: fn = module } = module;
|
||||
fn(tag, t);
|
||||
}
|
||||
|
||||
currentTag = currentTag.parentPath.parentPath;
|
||||
},
|
||||
|
||||
onfinish() {
|
||||
onNext = onNext && onNext();
|
||||
},
|
||||
|
||||
onError({ message, pos, endPos }) {
|
||||
if (message.includes("EOF")) endPos = pos;
|
||||
throw hub.buildError({ start: pos, end: endPos }, message);
|
||||
}
|
||||
};
|
||||
|
||||
createParser(handlers, {
|
||||
isOpenTagOnly(name) {
|
||||
const { parseOptions = EMPTY_OBJECT } =
|
||||
hub.lookup.getTag(name) || EMPTY_OBJECT;
|
||||
return parseOptions.openTagOnly;
|
||||
},
|
||||
ignoreNonstandardStringPlaceholders: true,
|
||||
...htmlParseOptions
|
||||
}).parse(code, filename);
|
||||
}
|
||||
47
packages/compiler/src/babel-plugin/plugins/migrate.js
Normal file
47
packages/compiler/src/babel-plugin/plugins/migrate.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import { getTagDef } from "@marko/babel-utils";
|
||||
import { enter, exit } from "../util/plugin-hooks";
|
||||
|
||||
/**
|
||||
* Applies custom migrators on tags.
|
||||
*/
|
||||
export const visitor = {
|
||||
MarkoTag: {
|
||||
enter(path) {
|
||||
const migrators = getMigratorsForTag(path);
|
||||
const { node } = path;
|
||||
for (const migrator of migrators) {
|
||||
enter(migrator, path, t);
|
||||
if (path.node !== node) break; // Stop if node is replaced.
|
||||
}
|
||||
},
|
||||
exit(path) {
|
||||
const migrators = getMigratorsForTag(path);
|
||||
const { node } = path;
|
||||
for (const migrator of migrators) {
|
||||
exit(migrator, path, t);
|
||||
if (path.node !== node) break; // Stop if node is replaced.
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getMigratorsForTag(path) {
|
||||
const { hub } = path;
|
||||
const { lookup } = hub;
|
||||
const tagName = path.get("name.value").node;
|
||||
const MIGRATOR_CACHE = (lookup.MIGRATOR_CACHE = lookup.MIGRATOR_CACHE || {});
|
||||
|
||||
let migrators = MIGRATOR_CACHE[tagName];
|
||||
|
||||
if (!migrators) {
|
||||
const tagDef = getTagDef(path);
|
||||
|
||||
migrators = MIGRATOR_CACHE[tagName] = [
|
||||
...(tagDef ? tagDef.migratorPaths : []),
|
||||
...(lookup.getTag("*") || { migratorPaths: [] }).migratorPaths
|
||||
].map(path => require(path));
|
||||
}
|
||||
|
||||
return migrators;
|
||||
}
|
||||
67
packages/compiler/src/babel-plugin/plugins/transform.js
Normal file
67
packages/compiler/src/babel-plugin/plugins/transform.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import { getTagDef } from "@marko/babel-utils";
|
||||
import { enter, exit } from "../util/plugin-hooks";
|
||||
|
||||
/**
|
||||
* Applies custom transformers on tags.
|
||||
*/
|
||||
export const visitor = {
|
||||
Program(path) {
|
||||
path.hub._componentDefIdentifier = path.scope.generateUidIdentifier(
|
||||
"component"
|
||||
);
|
||||
},
|
||||
MarkoTag: {
|
||||
enter(path) {
|
||||
const transformers = getTransformersForTag(path);
|
||||
const { node } = path;
|
||||
|
||||
for (const transformer of transformers) {
|
||||
enter(transformer, path, t);
|
||||
if (path.node !== node) break; // Stop if node is replaced.
|
||||
}
|
||||
},
|
||||
exit(path) {
|
||||
const transformers = getTransformersForTag(path);
|
||||
const { node } = path;
|
||||
|
||||
for (const transformer of transformers) {
|
||||
exit(transformer, path, t);
|
||||
if (path.node !== node) break; // Stop if node is replaced.
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getTransformersForTag(path) {
|
||||
const { hub } = path;
|
||||
const { lookup } = hub;
|
||||
const tagName = path.get("name.value").node || "*";
|
||||
const TRANSFORMER_CACHE = (lookup.TRANSFORMER_CACHE =
|
||||
lookup.TRANSFORMER_CACHE || {});
|
||||
|
||||
let transformers = TRANSFORMER_CACHE[tagName];
|
||||
|
||||
if (!transformers) {
|
||||
const tagDef = getTagDef(path);
|
||||
|
||||
transformers = TRANSFORMER_CACHE[tagName] = (tagDef
|
||||
? Object.values(tagDef.transformers)
|
||||
: []
|
||||
)
|
||||
.concat(
|
||||
Object.values((lookup.getTag("*") || { transformers: [] }).transformers)
|
||||
)
|
||||
.sort(comparePriority)
|
||||
.map(({ path }) => require(path));
|
||||
}
|
||||
|
||||
return transformers;
|
||||
}
|
||||
|
||||
function comparePriority(a, b) {
|
||||
a = a.priority || 0;
|
||||
b = b.priority || 0;
|
||||
|
||||
return a - b;
|
||||
}
|
||||
11
packages/compiler/src/babel-plugin/util/checksum.js
Normal file
11
packages/compiler/src/babel-plugin/util/checksum.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export default function checksum(filename) {
|
||||
const hash = createHash("sha1");
|
||||
hash.update(filename);
|
||||
return hash
|
||||
.digest("base64")
|
||||
.slice(0, 8)
|
||||
.replace(/\//g, "-")
|
||||
.replace(/\+/g, "_");
|
||||
}
|
||||
14
packages/compiler/src/babel-plugin/util/code-frame-error.js
Normal file
14
packages/compiler/src/babel-plugin/util/code-frame-error.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { relative } from "path";
|
||||
import { codeFrameColumns } from "@babel/code-frame";
|
||||
import { getLoc } from "./get-loc";
|
||||
const cwd = process.cwd();
|
||||
|
||||
export default (filename = "", code, msg, startPos, endPos) => {
|
||||
const start = getLoc(code, startPos);
|
||||
const end = endPos != null && getLoc(code, endPos);
|
||||
const frame = codeFrameColumns(code, { start, end }, { highlightCode: true });
|
||||
const position = `(${start.line},${start.column})`;
|
||||
return new SyntaxError(
|
||||
`${relative(cwd, filename)}${position}: ${msg}\n${frame}`
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { relative } from "path";
|
||||
import { codeFrameColumns } from "@babel/code-frame";
|
||||
import { getLoc } from "./get-loc";
|
||||
import complain from "complain";
|
||||
const cwd = process.cwd();
|
||||
|
||||
export default (filename = "", code, msg, node) => {
|
||||
const start = getLoc(code, node.start);
|
||||
const end = node.end != null && getLoc(code, node.end);
|
||||
const frame = codeFrameColumns(code, { start, end }, { highlightCode: true });
|
||||
const position = `(${start.line},${start.column})`;
|
||||
const location = node && node.pos;
|
||||
const options = { location };
|
||||
|
||||
if (location != null) {
|
||||
options.location = position;
|
||||
} else {
|
||||
options.location = filename;
|
||||
}
|
||||
|
||||
return complain(
|
||||
`${relative(cwd, filename)}${position}: ${msg}\n${frame}`,
|
||||
options
|
||||
);
|
||||
};
|
||||
21
packages/compiler/src/babel-plugin/util/create-file.js
Normal file
21
packages/compiler/src/babel-plugin/util/create-file.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { getLoc } from "./get-loc";
|
||||
|
||||
export default (filename, code) => {
|
||||
const opts = { filename };
|
||||
const start = { line: 0, column: 0 };
|
||||
const end = getLoc(code, code.length);
|
||||
const loc = { start, end, loc: { start, end } };
|
||||
return {
|
||||
type: "File",
|
||||
code,
|
||||
opts,
|
||||
...loc,
|
||||
program: {
|
||||
type: "Program",
|
||||
sourceType: "module",
|
||||
...loc,
|
||||
body: [],
|
||||
directives: []
|
||||
}
|
||||
};
|
||||
};
|
||||
28
packages/compiler/src/babel-plugin/util/get-loc.js
Normal file
28
packages/compiler/src/babel-plugin/util/get-loc.js
Normal file
@ -0,0 +1,28 @@
|
||||
const NEW_LINE = /\r?\n/g;
|
||||
|
||||
export function getLoc(code, pos) {
|
||||
const src = code.slice(0, pos);
|
||||
let lastIndex = 0;
|
||||
let line = 1;
|
||||
|
||||
while (NEW_LINE.exec(src)) {
|
||||
line++;
|
||||
lastIndex = NEW_LINE.lastIndex;
|
||||
}
|
||||
|
||||
return {
|
||||
line,
|
||||
column: pos - lastIndex + 1
|
||||
};
|
||||
}
|
||||
|
||||
export function getLocRange(code, start, end) {
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
loc: {
|
||||
start: getLoc(code, start),
|
||||
end: getLoc(code, end)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export default (hub, details) => {
|
||||
if (details) {
|
||||
return hub.parseExpression(`_(${details.value})`, details.pos - 1)
|
||||
.arguments;
|
||||
}
|
||||
};
|
||||
60
packages/compiler/src/babel-plugin/util/parse-attributes.js
Normal file
60
packages/compiler/src/babel-plugin/util/parse-attributes.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import parseArguments from "./parse-arguments";
|
||||
|
||||
export default (hub, attributes, startPos) => {
|
||||
const code = hub.getCode();
|
||||
let attrEndPos = startPos;
|
||||
|
||||
return attributes.map(attr => {
|
||||
const attrStartPos = code.indexOf(attr.name, attrEndPos);
|
||||
|
||||
if (attr.name.slice(0, 3) === "...") {
|
||||
let attrExpression = attr.name.slice(3);
|
||||
|
||||
if (attr.argument) {
|
||||
attrExpression += `(${attr.argument.value})`;
|
||||
}
|
||||
|
||||
attrEndPos = attrStartPos + attrExpression.length;
|
||||
|
||||
const value = hub.parseExpression(attrExpression, attrStartPos + 3);
|
||||
|
||||
// TODO: Inline merge object literals.
|
||||
return hub.createNode(
|
||||
"markoSpreadAttribute",
|
||||
attrStartPos,
|
||||
attrEndPos,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
const match = /:(.*)$/.exec(attr.name);
|
||||
const modifier = match && match[1];
|
||||
let name = attr.name;
|
||||
let value;
|
||||
|
||||
if (modifier) {
|
||||
name = name.slice(0, name.length - modifier.length - 1);
|
||||
}
|
||||
|
||||
if (attr.value) {
|
||||
attrEndPos = attr.endPos;
|
||||
const valueStart = attr.pos + 1; // Add one to account for "=".
|
||||
const rawValue = code.slice(valueStart, attrEndPos); // We use the raw value to ignore things like non standard placeholders.
|
||||
value = hub.parseExpression(rawValue, valueStart);
|
||||
} else {
|
||||
attrEndPos = attr.argument ? attr.argument.endPos + 1 : attr.endPos;
|
||||
value = t.booleanLiteral(true);
|
||||
}
|
||||
|
||||
return hub.createNode(
|
||||
"markoAttribute",
|
||||
attrStartPos,
|
||||
attrEndPos,
|
||||
name,
|
||||
value,
|
||||
modifier,
|
||||
parseArguments(hub, attr.argument)
|
||||
);
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
|
||||
export default (hub, shorthands, attributes) => {
|
||||
if (!shorthands) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
const classAttr = attributes.find(({ name }) => name === "class");
|
||||
const classParts = shorthands.map(({ rawParts }) => {
|
||||
const nodes = rawParts.map(part =>
|
||||
part.expression
|
||||
? hub.parseExpression(part.expression, part.pos)
|
||||
: hub.createNode("stringLiteral", part.pos, part.endPos, part.text)
|
||||
);
|
||||
|
||||
if (nodes.length === 1) {
|
||||
return nodes[0];
|
||||
}
|
||||
|
||||
return nodes.reduce((a, b) => t.binaryExpression("+", a, b));
|
||||
});
|
||||
|
||||
let shorthandNode;
|
||||
if (classParts.length === 1) {
|
||||
shorthandNode = classParts[0];
|
||||
} else if (classParts.every(node => t.isStringLiteral(node))) {
|
||||
const combinedStartPos = shorthands[0].rawParts[0].pos;
|
||||
const lastParts = shorthands[shorthands.length - 1].rawParts;
|
||||
const combinedEndPos = lastParts[lastParts.length - 1].endPos;
|
||||
shorthandNode = hub.createNode(
|
||||
"stringLiteral",
|
||||
combinedStartPos,
|
||||
combinedEndPos,
|
||||
classParts.map(node => node.value).join(" ")
|
||||
);
|
||||
} else {
|
||||
shorthandNode = t.arrayExpression(classParts);
|
||||
}
|
||||
|
||||
if (classAttr) {
|
||||
if (t.isArrayExpression(classAttr.value)) {
|
||||
if (t.isArrayExpression(shorthandNode)) {
|
||||
classAttr.value.elements.push(...shorthandNode.elements);
|
||||
} else {
|
||||
classAttr.value.elements.push(shorthandNode);
|
||||
}
|
||||
} else if (
|
||||
t.isStringLiteral(classAttr.value) &&
|
||||
t.isStringLiteral(shorthandNode)
|
||||
) {
|
||||
classAttr.value.value = `${shorthandNode.value} ${classAttr.value.value}`;
|
||||
} else if (t.isArrayExpression(shorthandNode)) {
|
||||
shorthandNode.elements.push(classAttr.value);
|
||||
classAttr.value = shorthandNode;
|
||||
} else {
|
||||
classAttr.value = t.arrayExpression([shorthandNode, classAttr.value]);
|
||||
}
|
||||
} else {
|
||||
attributes.push(t.markoAttribute("class", shorthandNode));
|
||||
}
|
||||
|
||||
return attributes;
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
|
||||
export default (hub, shorthand, attributes) => {
|
||||
if (!shorthand) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
const idAttr = attributes.find(({ name }) => name === "id");
|
||||
if (idAttr) {
|
||||
throw hub.buildError(idAttr, "Cannot have shorthand id and id attribute.");
|
||||
}
|
||||
|
||||
const idParts = shorthand.rawParts.map(part =>
|
||||
part.expression
|
||||
? hub.parseExpression(part.expression, part.pos)
|
||||
: hub.createNode("stringLiteral", part.pos, part.endPos, part.text)
|
||||
);
|
||||
|
||||
attributes.push(
|
||||
t.markoAttribute(
|
||||
"id",
|
||||
idParts.length === 1
|
||||
? idParts[0]
|
||||
: idParts.reduce((a, b) => t.binaryExpression("+", a, b))
|
||||
)
|
||||
);
|
||||
|
||||
return attributes;
|
||||
};
|
||||
5
packages/compiler/src/babel-plugin/util/parse-params.js
Normal file
5
packages/compiler/src/babel-plugin/util/parse-params.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default (hub, details) => {
|
||||
if (details) {
|
||||
return hub.parseExpression(`(${details.value})=>{}`, details.pos).params;
|
||||
}
|
||||
};
|
||||
22
packages/compiler/src/babel-plugin/util/plugin-hooks.js
Normal file
22
packages/compiler/src/babel-plugin/util/plugin-hooks.js
Normal file
@ -0,0 +1,22 @@
|
||||
// Utilities for executing multi step compiler hooks (migrators & transformers).
|
||||
|
||||
export function enter(plugin, ...args) {
|
||||
const fn =
|
||||
(plugin &&
|
||||
(plugin.enter ||
|
||||
(plugin.default && plugin.default.enter) ||
|
||||
plugin.default)) ||
|
||||
plugin;
|
||||
if (typeof fn === "function") {
|
||||
fn(...args);
|
||||
}
|
||||
}
|
||||
|
||||
export function exit(plugin, ...args) {
|
||||
const fn =
|
||||
plugin &&
|
||||
(plugin.exit || (plugin.default ? plugin.default.exit : undefined));
|
||||
if (typeof fn === "function") {
|
||||
fn(...args);
|
||||
}
|
||||
}
|
||||
45
packages/compiler/src/config.js
Normal file
45
packages/compiler/src/config.js
Normal file
@ -0,0 +1,45 @@
|
||||
let config;
|
||||
const globalThis = typeof window === "undefined" ? global : window;
|
||||
const MARKO_CONFIG_KEY = Symbol("Default Marko Compiler Config");
|
||||
|
||||
if (globalThis[MARKO_CONFIG_KEY]) {
|
||||
config = globalThis[MARKO_CONFIG_KEY];
|
||||
} else {
|
||||
config = globalThis[MARKO_CONFIG_KEY] = {
|
||||
// The default output mode for compiled templates
|
||||
output: "html",
|
||||
|
||||
/**
|
||||
* Whether the version should be written to the template as a comment e.g.
|
||||
* // Compiled using marko@4.0.0 - DO NOT EDIT
|
||||
*/
|
||||
writeVersionComment: true,
|
||||
|
||||
/**
|
||||
* Whether unrecognized tags should be ignored or not. This flag will
|
||||
* be enabled by default when compiling XML.
|
||||
*/
|
||||
ignoreUnrecognizedTags: false,
|
||||
|
||||
/**
|
||||
* Whether source maps should be output with the compiled templates.
|
||||
* When `true` a `map` property will be available on the compile result.
|
||||
* When `"inline"` the sourcemap will be inlined as a comment in the output code.
|
||||
* When `"both"` both of the above will be used.
|
||||
*/
|
||||
sourceMaps: false,
|
||||
|
||||
/**
|
||||
* This option inlines all of the meta data in the template.
|
||||
* You can also access this metadata via `compile(...).meta`.
|
||||
* This API is sticking around for compatibility purposes.
|
||||
*/
|
||||
meta: false
|
||||
};
|
||||
|
||||
if (process.env.MARKO_CONFIG) {
|
||||
Object.assign(config, JSON.parse(process.env.MARKO_CONFIG));
|
||||
}
|
||||
}
|
||||
|
||||
export default config;
|
||||
65
packages/compiler/src/index.js
Normal file
65
packages/compiler/src/index.js
Normal file
@ -0,0 +1,65 @@
|
||||
import fs from "fs";
|
||||
import { loadPartialConfig, transformAsync, transformSync } from "@babel/core";
|
||||
import corePlugin from "./babel-plugin";
|
||||
import defaultOptions from "./config";
|
||||
import * as taglib from "./taglib";
|
||||
|
||||
export { taglib };
|
||||
|
||||
let globalConfig = Object.assign({}, defaultOptions);
|
||||
export function configure(newConfig = {}) {
|
||||
globalConfig = Object.assign({}, defaultOptions, newConfig);
|
||||
}
|
||||
|
||||
export async function compile(src, filename, options) {
|
||||
const babelConfig = loadBabelConfig(filename, options);
|
||||
const babelResult = await transformAsync(src, babelConfig);
|
||||
return buildResult(babelResult);
|
||||
}
|
||||
|
||||
export function compileSync(src, filename, options) {
|
||||
const babelConfig = loadBabelConfig(filename, options);
|
||||
const babelResult = transformSync(src, babelConfig);
|
||||
return buildResult(babelResult);
|
||||
}
|
||||
|
||||
export async function compileFile(filename, options) {
|
||||
const src = await fs.promises.readFile(filename, "utf-8");
|
||||
return compile(src, filename, options);
|
||||
}
|
||||
|
||||
export function compileFileSync(filename, options) {
|
||||
const src = fs.readFileSync(filename, "utf-8");
|
||||
return compileSync(src, filename, options);
|
||||
}
|
||||
|
||||
function loadBabelConfig(filename, options) {
|
||||
const markoConfig = Object.assign({}, globalConfig);
|
||||
|
||||
if (options) {
|
||||
Object.assign(markoConfig, options);
|
||||
}
|
||||
|
||||
const baseBabelConfig = {
|
||||
filename: filename,
|
||||
sourceFileName: filename,
|
||||
sourceType: "module",
|
||||
sourceMaps: markoConfig.sourceMaps,
|
||||
plugins: [[corePlugin, markoConfig]]
|
||||
};
|
||||
|
||||
if (markoConfig.babelConfig) {
|
||||
Object.assign(baseBabelConfig, markoConfig.babelConfig);
|
||||
}
|
||||
|
||||
return loadPartialConfig(baseBabelConfig).options;
|
||||
}
|
||||
|
||||
function buildResult(babelResult) {
|
||||
const {
|
||||
map,
|
||||
code,
|
||||
metadata: { marko: meta }
|
||||
} = babelResult;
|
||||
return { map, code, meta };
|
||||
}
|
||||
2294
packages/compiler/src/taglib/html/marko.json
Normal file
2294
packages/compiler/src/taglib/html/marko.json
Normal file
File diff suppressed because it is too large
Load Diff
46
packages/compiler/src/taglib/index.js
Normal file
46
packages/compiler/src/taglib/index.js
Normal file
@ -0,0 +1,46 @@
|
||||
import path from "path";
|
||||
import loader from "marko/src/taglib/taglib-loader";
|
||||
import finder from "marko/src/taglib/taglib-finder";
|
||||
import TaglibLookup from "marko/src/taglib/taglib-lookup/TaglibLookup";
|
||||
|
||||
export { excludeDir, excludePackage } from "marko/src/taglib/taglib-finder";
|
||||
|
||||
let lookupCache = Object.create(null);
|
||||
const coreTaglibs = ["html", "svg", "math"].map(name =>
|
||||
loader.loadTaglibFromFile(path.join(__dirname, name, "marko.json"))
|
||||
);
|
||||
|
||||
export function buildLookup(dirname, translator = "default") {
|
||||
const translatorTaglibs = Array.isArray(translator)
|
||||
? translator
|
||||
: require(`@marko/translator-${translator}`).taglibs;
|
||||
const taglibsForDir = finder.find(
|
||||
dirname,
|
||||
coreTaglibs.concat(translatorTaglibs)
|
||||
);
|
||||
|
||||
const cacheKey = taglibsForDir.map(it => it.id).join();
|
||||
let lookup = lookupCache[cacheKey];
|
||||
|
||||
if (!lookup) {
|
||||
lookup = lookupCache[cacheKey] = new TaglibLookup();
|
||||
for (const taglib of taglibsForDir) {
|
||||
lookup.addTaglib(taglib);
|
||||
if (taglib.imports) {
|
||||
for (const importedTaglib of taglib.imports) {
|
||||
if (!lookup.hasTaglib(importedTaglib)) {
|
||||
lookup.addTaglib(importedTaglib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
export function clearCaches() {
|
||||
loader.clearCache();
|
||||
finder.clearCache();
|
||||
lookupCache = Object.create(null);
|
||||
}
|
||||
45
packages/compiler/src/taglib/math/marko.json
Normal file
45
packages/compiler/src/taglib/math/marko.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"taglib-id": "marko-math",
|
||||
"<math>": { "html": true, "htmlType": "math" },
|
||||
"<maction>": { "html": true, "htmlType": "math" },
|
||||
"<maligngroup>": { "html": true, "htmlType": "math" },
|
||||
"<malignmark>": { "html": true, "htmlType": "math" },
|
||||
"<menclose>": { "html": true, "htmlType": "math" },
|
||||
"<merror>": { "html": true, "htmlType": "math" },
|
||||
"<mfenced>": { "html": true, "htmlType": "math" },
|
||||
"<mfrac>": { "html": true, "htmlType": "math" },
|
||||
"<mglyph>": { "html": true, "htmlType": "math" },
|
||||
"<mi>": { "html": true, "htmlType": "math" },
|
||||
"<mlabeledtr>": { "html": true, "htmlType": "math" },
|
||||
"<mlongdiv>": { "html": true, "htmlType": "math" },
|
||||
"<mmultiscripts>": { "html": true, "htmlType": "math" },
|
||||
"<mn>": { "html": true, "htmlType": "math" },
|
||||
"<mo>": { "html": true, "htmlType": "math" },
|
||||
"<mover>": { "html": true, "htmlType": "math" },
|
||||
"<mpadded>": { "html": true, "htmlType": "math" },
|
||||
"<mphantom>": { "html": true, "htmlType": "math" },
|
||||
"<mroot>": { "html": true, "htmlType": "math" },
|
||||
"<mrow>": { "html": true, "htmlType": "math" },
|
||||
"<ms>": { "html": true, "htmlType": "math" },
|
||||
"<mscarries>": { "html": true, "htmlType": "math" },
|
||||
"<mscarry>": { "html": true, "htmlType": "math" },
|
||||
"<msgroup>": { "html": true, "htmlType": "math" },
|
||||
"<mstack>": { "html": true, "htmlType": "math" },
|
||||
"<msline>": { "html": true, "htmlType": "math" },
|
||||
"<mspace>": { "html": true, "htmlType": "math" },
|
||||
"<msqrt>": { "html": true, "htmlType": "math" },
|
||||
"<msrow>": { "html": true, "htmlType": "math" },
|
||||
"<mstyle>": { "html": true, "htmlType": "math" },
|
||||
"<msub>": { "html": true, "htmlType": "math" },
|
||||
"<msup>": { "html": true, "htmlType": "math" },
|
||||
"<msubsup>": { "html": true, "htmlType": "math" },
|
||||
"<mtable>": { "html": true, "htmlType": "math" },
|
||||
"<mtd>": { "html": true, "htmlType": "math" },
|
||||
"<mtext>": { "html": true, "htmlType": "math" },
|
||||
"<mtr>": { "html": true, "htmlType": "math" },
|
||||
"<munder>": { "html": true, "htmlType": "math" },
|
||||
"<munderover>": { "html": true, "htmlType": "math" },
|
||||
"<semantics>": { "html": true, "htmlType": "math" },
|
||||
"<mprescripts>": { "html": true, "htmlType": "math" },
|
||||
"<none>": { "html": true, "htmlType": "math" }
|
||||
}
|
||||
62
packages/compiler/src/taglib/svg/marko.json
Normal file
62
packages/compiler/src/taglib/svg/marko.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"taglib-id": "marko-svg",
|
||||
"<animate>": { "html": true, "htmlType": "svg" },
|
||||
"<animateColor>": { "html": true, "htmlType": "svg" },
|
||||
"<animateMotion>": { "html": true, "htmlType": "svg" },
|
||||
"<animateTransform>": { "html": true, "htmlType": "svg" },
|
||||
"<circle>": { "html": true, "htmlType": "svg" },
|
||||
"<clipPath>": { "html": true, "htmlType": "svg" },
|
||||
"<defs>": { "html": true, "htmlType": "svg" },
|
||||
"<desc>": { "html": true, "htmlType": "svg" },
|
||||
"<ellipse>": { "html": true, "htmlType": "svg" },
|
||||
"<feBlend>": { "html": true, "htmlType": "svg" },
|
||||
"<feColorMatrix>": { "html": true, "htmlType": "svg" },
|
||||
"<feComponentTransfer>": { "html": true, "htmlType": "svg" },
|
||||
"<feComposite>": { "html": true, "htmlType": "svg" },
|
||||
"<feConvolveMatrix>": { "html": true, "htmlType": "svg" },
|
||||
"<feDiffuseLighting>": { "html": true, "htmlType": "svg" },
|
||||
"<feDisplacementMap>": { "html": true, "htmlType": "svg" },
|
||||
"<feDistantLight>": { "html": true, "htmlType": "svg" },
|
||||
"<feFlood>": { "html": true, "htmlType": "svg" },
|
||||
"<feFuncA>": { "html": true, "htmlType": "svg" },
|
||||
"<feFuncB>": { "html": true, "htmlType": "svg" },
|
||||
"<feFuncG>": { "html": true, "htmlType": "svg" },
|
||||
"<feFuncR>": { "html": true, "htmlType": "svg" },
|
||||
"<feGaussianBlur>": { "html": true, "htmlType": "svg" },
|
||||
"<feImage>": { "html": true, "htmlType": "svg" },
|
||||
"<feMerge>": { "html": true, "htmlType": "svg" },
|
||||
"<feMergeNode>": { "html": true, "htmlType": "svg" },
|
||||
"<feMorphology>": { "html": true, "htmlType": "svg" },
|
||||
"<feOffset>": { "html": true, "htmlType": "svg" },
|
||||
"<fePointLight>": { "html": true, "htmlType": "svg" },
|
||||
"<feSpecularLighting>": { "html": true, "htmlType": "svg" },
|
||||
"<feSpotLight>": { "html": true, "htmlType": "svg" },
|
||||
"<feTile>": { "html": true, "htmlType": "svg" },
|
||||
"<feTurbulence>": { "html": true, "htmlType": "svg" },
|
||||
"<filter>": { "html": true, "htmlType": "svg" },
|
||||
"<foreignObject>": { "html": true, "htmlType": "svg" },
|
||||
"<g>": { "html": true, "htmlType": "svg" },
|
||||
"<image>": { "html": true, "htmlType": "svg" },
|
||||
"<line>": { "html": true, "htmlType": "svg" },
|
||||
"<linearGradient>": { "html": true, "htmlType": "svg" },
|
||||
"<marker>": { "html": true, "htmlType": "svg" },
|
||||
"<mask>": { "html": true, "htmlType": "svg" },
|
||||
"<metadata>": { "html": true, "htmlType": "svg" },
|
||||
"<mpath>": { "html": true, "htmlType": "svg" },
|
||||
"<path>": { "html": true, "htmlType": "svg" },
|
||||
"<pattern>": { "html": true, "htmlType": "svg" },
|
||||
"<polygon>": { "html": true, "htmlType": "svg" },
|
||||
"<polyline>": { "html": true, "htmlType": "svg" },
|
||||
"<radialGradient>": { "html": true, "htmlType": "svg" },
|
||||
"<rect>": { "html": true, "htmlType": "svg" },
|
||||
"<set>": { "html": true, "htmlType": "svg" },
|
||||
"<stop>": { "html": true, "htmlType": "svg" },
|
||||
"<svg>": { "html": true, "htmlType": "svg" },
|
||||
"<switch>": { "html": true, "htmlType": "svg" },
|
||||
"<symbol>": { "html": true, "htmlType": "svg" },
|
||||
"<text>": { "html": true, "htmlType": "svg" },
|
||||
"<textPath>": { "html": true, "htmlType": "svg" },
|
||||
"<tspan>": { "html": true, "htmlType": "svg" },
|
||||
"<use>": { "html": true, "htmlType": "svg" },
|
||||
"<view>": { "html": true, "htmlType": "svg" }
|
||||
}
|
||||
63
packages/compiler/test/index.test.js
Normal file
63
packages/compiler/test/index.test.js
Normal file
@ -0,0 +1,63 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import autotest from "mocha-autotest";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { compileFileSync } from "../src";
|
||||
|
||||
fs.readdirSync(path.join(__dirname, "../../"))
|
||||
.map(dir => /^translator-(.*)|/.exec(dir)[1])
|
||||
.filter(Boolean)
|
||||
.forEach(translator => {
|
||||
autotest(path.normalize(`../../translator-${translator}/test/fixtures`), {
|
||||
html: runTest({ output: "html" }),
|
||||
vdom: runTest({ output: "dom" }),
|
||||
generated: runTest({ _parseOnly: true })
|
||||
});
|
||||
|
||||
function runTest(config) {
|
||||
return ({ mode, test, resolve, snapshot }) => {
|
||||
const testConfigFile = resolve("test.js");
|
||||
const testConfig = fs.existsSync(testConfigFile)
|
||||
? require(testConfigFile)
|
||||
: {};
|
||||
const templateFile = resolve(
|
||||
testConfig.templateFile || "template.marko"
|
||||
);
|
||||
|
||||
const compilerConfig = {
|
||||
...config,
|
||||
babelConfig: {
|
||||
babelrc: false,
|
||||
configFile: false
|
||||
},
|
||||
writeVersionComment: false
|
||||
};
|
||||
|
||||
test(() => {
|
||||
let output;
|
||||
try {
|
||||
output = compileFileSync(templateFile, compilerConfig).code;
|
||||
} catch (err) {
|
||||
try {
|
||||
snapshot(stripCwd(stripAnsi(err.message)), {
|
||||
name: `${mode}-error`,
|
||||
ext: ".txt"
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
snapshot(output, {
|
||||
name: mode,
|
||||
ext: mode === "generated" ? ".marko" : ".js"
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function stripCwd(message) {
|
||||
return message.replace(process.cwd() + "/", "");
|
||||
}
|
||||
37
packages/translator-default/package.json
Normal file
37
packages/translator-default/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@marko/translator-default",
|
||||
"description": "Translates Marko templates to the default Marko runtime.",
|
||||
"version": "5.0.0",
|
||||
"author": "Dylan Piercey <dpiercey@ebay.com>",
|
||||
"bugs": "https://github.com/marko-js/marko/issues/new?template=Bug_report.md",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@marko/babel-types": "^5.0.0",
|
||||
"@marko/babel-utils": "^5.0.0",
|
||||
"marko": "^5.0.0",
|
||||
"self-closing-tags": "^1.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://github.com/marko-js/marko/blob/master/packages/translator-default/README.md",
|
||||
"keywords": [
|
||||
"htmljs",
|
||||
"parser",
|
||||
"babel",
|
||||
"plugin",
|
||||
"parse",
|
||||
"marko"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"main:dev": "src/index.js",
|
||||
"main:npm": "dist/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marko-js/marko/tree/master/packages/translator-default"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
13
packages/translator-default/src/cdata/index.js
Normal file
13
packages/translator-default/src/cdata/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import translateHTML from "./index[html]";
|
||||
import translateVDOM from "./index[vdom]";
|
||||
|
||||
export default function(path) {
|
||||
const {
|
||||
hub: { options }
|
||||
} = path;
|
||||
if (options.output === "html") {
|
||||
translateHTML(path);
|
||||
} else {
|
||||
translateVDOM(path);
|
||||
}
|
||||
}
|
||||
8
packages/translator-default/src/cdata/index[html].js
Normal file
8
packages/translator-default/src/cdata/index[html].js
Normal file
@ -0,0 +1,8 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import write from "../util/html-out-write";
|
||||
|
||||
export default function(path) {
|
||||
const { node } = path;
|
||||
|
||||
path.replaceWith(write`<![CDATA[${t.stringLiteral(node.value)}]]>`);
|
||||
}
|
||||
8
packages/translator-default/src/cdata/index[vdom].js
Normal file
8
packages/translator-default/src/cdata/index[vdom].js
Normal file
@ -0,0 +1,8 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
import write from "../util/vdom-out-write";
|
||||
|
||||
export default function(path) {
|
||||
const { node } = path;
|
||||
|
||||
path.replaceWith(write("t", t.stringLiteral(node.value)));
|
||||
}
|
||||
60
packages/translator-default/src/class.js
Normal file
60
packages/translator-default/src/class.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { types as t } from "@marko/babel-types";
|
||||
|
||||
export default function(path) {
|
||||
const {
|
||||
hub,
|
||||
node: {
|
||||
body: { body }
|
||||
}
|
||||
} = path;
|
||||
|
||||
const classProperties = [];
|
||||
let onCreateMethod = body.find(
|
||||
prop =>
|
||||
prop.computed === false &&
|
||||
t.isIdentifier(prop.key) &&
|
||||
prop.key.name === "onCreate"
|
||||
);
|
||||
|
||||
const objectProperties = body
|
||||
.map(prop => {
|
||||
if (t.isClassMethod(prop)) {
|
||||
prop.type = "ObjectMethod";
|
||||
delete prop.start;
|
||||
delete prop.end;
|
||||
delete prop.loc;
|
||||
return prop;
|
||||
} else if (t.isClassProperty(prop) && !prop.static) {
|
||||
classProperties.push(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(t.thisExpression(), prop.key, prop.computed),
|
||||
prop.value
|
||||
)
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
throw hub.buildError(prop, "Unsupported class property on component.");
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (classProperties.length) {
|
||||
if (!onCreateMethod) {
|
||||
objectProperties.push(
|
||||
(onCreateMethod = t.objectMethod(
|
||||
"method",
|
||||
t.identifier("onCreate"),
|
||||
[],
|
||||
t.blockStatement([])
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
onCreateMethod.body.body.unshift(...classProperties);
|
||||
}
|
||||
|
||||
hub.inlineComponentClass = t.objectExpression(objectProperties);
|
||||
path.remove();
|
||||
}
|
||||
13
packages/translator-default/src/comment/index.js
Normal file
13
packages/translator-default/src/comment/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import translateHTML from "./index[html]";
|
||||
import translateVDOM from "./index[vdom]";
|
||||
|
||||
export default function(path) {
|
||||
const {
|
||||
hub: { options }
|
||||
} = path;
|
||||
if (options.output === "html") {
|
||||
translateHTML(path);
|
||||
} else {
|
||||
translateVDOM(path);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user