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:
Michael Rawlings 2020-02-24 18:21:48 -08:00
parent ea6736d085
commit 02670c8693
No known key found for this signature in database
GPG Key ID: B9088328804D407C
750 changed files with 26522 additions and 10272 deletions

1
.browserslistrc Normal file
View File

@ -0,0 +1 @@
extends @ebay/browserslist-config

6
.commitlintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": [
"@commitlint/config-lerna-scopes",
"@commitlint/config-conventional"
]
}

View File

@ -1,18 +1,13 @@
# minified
*.min.js *.min.js
*.html.js
# test *.marko.js
*actual* *actual*
*expected* *expected*
input.js .cache/
.nyc_output/
# generated node_modules/
*dist* coverage/
*generated* *dist/
*.marko.js *generated/
*.marko.*.js
*.html.js
~vdom.skip ~vdom.skip
coverage **/test/**/input.js
node_modules

View File

@ -1,11 +1,19 @@
{ {
"extends": ["eslint:recommended", "prettier"], "extends": ["eslint:recommended", "prettier"],
"env": { "parserOptions": {
"node": true, "ecmaVersion": 2019,
"es6": true "sourceType": "module",
"ecmaFeatures": {
"jsx": false
}
}, },
"globals": { "rules": {
"document": true, "no-console": "off"
"ShadowRoot": true },
"env": {
"browser": true,
"node": true,
"es6": true,
"mocha": true
} }
} }

133
.gitignore vendored
View File

@ -1,22 +1,117 @@
/work ### Project ###
/build
.idea/
npm-debug.log
node_modules
*.sublime-workspace
*.orig
.DS_Store
.vscode
coverage
.nvmrc
~*
/.cache
*.marko.js *.marko.js
*.marko.xml.js *actual.*
/test/generated/ 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 .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 *.swp
/**/dist/ *.vim
/**/test-dist/ .netrwhist
/**/test-generated/
.vs/

View File

@ -1,7 +1,6 @@
{ {
"hooks": { "hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
} }

View File

@ -1,4 +1,4 @@
{ {
"*.{json,css,md}": ["prettier --write", "git add"], "*.{json,css,md}": ["prettier --write", "git add"],
"*.js": ["eslint", "prettier --write", "git add"] "*.js": ["eslint", "prettier --write", "git add"]
} }

View File

@ -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
View File

@ -0,0 +1,16 @@
{
"all": true,
"cache": true,
"include": [
"packages/*/src/**/*.js"
],
"instrument": false,
"reporter": [
"lcov",
"text-summary"
],
"require": [
"@babel/register"
],
"sourceMap": false
}

View File

@ -7,17 +7,20 @@
input.* input.*
# generated # generated
/**/dist/ dist/
/**/test-dist/
/**/test-generated/
*.marko.js *.marko.js
*.html.js *.html.js
*.xml.js *.xml.js
*.generated.js *.generated.js
.nyc_output .nyc_output
.cache/
coverage coverage
~* ~*
# controlled by npm's formatter # controlled by npm's formatter
package-lock.json package-lock.json
package.json package.json
# controlled by lerna
CHANGELOG.md
lerna.json

View File

@ -1,10 +1,8 @@
sudo: false sudo: false
node_js: node_js:
- "8"
- "10" - "10"
- "12" - "12"
language: node_js language: node_js
before_install: "npm i -g npm@latest"
install: "npm ci" install: "npm ci"
script: "npm run test-ci" script: "npm run ci:test"
after_success: "npm run codecov" after_success: "npm run ci:codecov"

View File

@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

35
babel.config.js Normal file
View 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"]
}
}
});

View File

@ -1,8 +0,0 @@
{
"rules": {
"no-console": 0
},
"parserOptions": {
"sourceType": "module"
}
}

View File

@ -1 +0,0 @@
static/

View File

@ -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);
};

View File

@ -1,2 +0,0 @@
/build/
/node_modules/

View File

@ -1,6 +0,0 @@
import Inferno, { render } from 'inferno';
import App from './components/App';
render(
<App name='Frank' colors={['red', 'green', 'blue']}/>,
document.body);

View File

@ -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>
);
}
};

View File

@ -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")
};

View File

@ -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>

View File

@ -1,7 +0,0 @@
var app = require("./components/app");
app
.renderSync({
name: "Frank",
colors: ["red", "green", "blue"]
})
.appendTo(document.body);

View File

@ -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)

View File

@ -1,3 +0,0 @@
var helpers = require("marko/src/runtime/vdom/helpers");
console.log("HELPERS:", helpers);

View File

@ -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")
};

View File

@ -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.");
});

View File

@ -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": {}
}

View File

@ -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>

View File

@ -1,10 +0,0 @@
{
"presets": [
["es2015", { "loose": true, "modules": false }],
"stage-0"
],
"plugins": [
[ "transform-react-jsx", { "pragma": "h" } ],
["transform-es2015-block-scoping"]
]
}

View File

@ -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);

View File

@ -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>
);
}
};

View File

@ -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")
};

View File

@ -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>

View File

@ -1,8 +0,0 @@
{
"presets": [
["es2015", { "loose": true, "modules": false }],
"stage-0",
"react"
],
"plugins": ["transform-react-constant-elements"]
}

View File

@ -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);

View File

@ -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>
);
}
};

View File

@ -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")
};

View File

@ -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>

View File

@ -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);
}
});

View File

@ -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>

View File

@ -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")
};

View File

@ -1,5 +0,0 @@
{
"env": {
"browser": true
}
}

View File

@ -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;
};
};

View File

@ -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);
};
};

View File

@ -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"
]
}

View File

@ -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();
});
}
});

View File

@ -1 +0,0 @@
benchmark-*

View File

@ -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)};
`;
};

View File

@ -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;
};

View File

@ -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;
`;
};

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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);
});
});

View File

@ -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>

View File

@ -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");
});

View File

@ -1,3 +1,6 @@
module.exports = { module.exports = {
extends: ["@commitlint/config-conventional"] extends: [
"@commitlint/config-conventional",
"@commitlint/config-lerna-scopes"
]
}; };

17
lerna.json Normal file
View File

@ -0,0 +1,17 @@
{
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish"
}
},
"packages": [
"packages/*"
],
"ignoreChanges": [
"**/package-lock.json",
"packages/*/test/**",
"**/*.md"
],
"version": "independent"
}

20217
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,55 @@
{ {
"name": "marko", "name": "marko",
"version": "4.0.0", "private": true,
"private": true, "scripts": {
"scripts": { "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": "node scripts/build.js", "build:watch": "npm run build -- --watch",
"build-src": "node scripts/build.js src", "postinstall": "lerna bootstrap --hoist --no-ci",
"postinstall": "cd packages/marko && npm i", "postrelease": "ENTRY=main:dev npm run set-entry",
"release": "npm run build-src && (cd ./packages/marko && standard-version && git push --follow-tags && npm publish)", "prerelease": "ENTRY=main:npm npm run set-entry",
"test": "mocha --timeout 10000 ./packages/marko/test/*/*.test.js", "release": "npm run build && lerna publish",
"test-ci": "npm run lint && npm run check-format && npm run test-generate-coverage", "test": "mocha -r @babel/register \"packages/*/test/{*.test.js,*/*.test.js}\"",
"test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && opn ./coverage/index.html", "test:watch": "npm run mocha -- --recursive --watch",
"test-generate-coverage": "nyc -asc npm test", "clean": "lerna clean && rm -rf ./packages/*/dist",
"lint": "eslint .", "lint": "eslint -f visualstudio .",
"format": "prettier \"**/*.{js,json,css,md}\" --write", "format": "prettier \"**/*.{json,md,js}\" --write",
"check-format": "prettier \"**/*.{js,json,css,md}\" -l", "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",
"devDependencies": { "report": "nyc report --reporter=html && open ./coverage/index.html",
"@commitlint/cli": "^8.3.5", "set-entry": "lerna exec -- dot-json package.json main $\\(dot-json package.json $ENTRY\\)"
"@commitlint/config-conventional": "^8.3.4", },
"babel-cli": "^6.24.1", "devDependencies": {
"babel-core": "^6.24.1", "@babel/cli": "^7.7.7",
"babel-plugin-minprops": "^2.0.1", "@babel/core": "^7.7.7",
"benchmark": "^2.1.1", "@babel/helper-module-imports": "^7.7.4",
"codecov": "^3.0.2", "@babel/node": "^7.7.7",
"eslint": "^4.11.0", "@babel/plugin-proposal-class-properties": "^7.7.4",
"eslint-config-prettier": "^2.9.0", "@babel/plugin-proposal-export-default-from": "^7.7.4",
"husky": "^4.2.3", "@babel/plugin-proposal-export-namespace-from": "^7.7.4",
"lint-staged": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.7.7",
"micromatch": "^3.0.4", "@babel/plugin-transform-runtime": "^7.7.6",
"mocha": "^5.0.1", "@babel/preset-env": "^7.7.7",
"nyc": "^13.0.0", "@babel/register": "^7.7.7",
"prettier": "^1.13.5", "@commitlint/cli": "^8.2.0",
"shelljs": "^0.7.7", "@commitlint/config-conventional": "^8.2.0",
"standard-version": "^7.1.0" "@commitlint/config-lerna-scopes": "^8.2.0",
}, "@ebay/browserslist-config": "^1.0.1",
"nyc": { "babel-plugin-istanbul": "^6.0.0",
"exclude": [ "babel-plugin-minprops": "^2.0.1",
"**/benchmark/**", "chai": "^4.2.0",
"**/scripts/**", "codecov": "^3.6.1",
"**/coverage/**", "cross-env": "^6.0.3",
"**/test/**", "dot-json": "^1.1.0",
"**/test-dist/**", "eslint": "^6.8.0",
"**/test-generated/**", "eslint-config-prettier": "^6.10.0",
"**/dist/**" "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"
}
} }

View 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"
}
}

View 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";

View 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
}
}
}
};

View 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);
}

View File

@ -0,0 +1,3 @@
import "./generators";
import * as types from "./definitions";
export { types };

View 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"
}
}

View 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.");
}
}

View 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";

View 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];
}
}
}

View 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 === "";
}

View 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"
}
}

View 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/");
}

View 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;
}
};
};

View 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);
}

View 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;
}

View 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;
}

View 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, "_");
}

View 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}`
);
};

View File

@ -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
);
};

View 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: []
}
};
};

View 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)
}
};
}

View File

@ -0,0 +1,6 @@
export default (hub, details) => {
if (details) {
return hub.parseExpression(`_(${details.value})`, details.pos - 1)
.arguments;
}
};

View 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)
);
});
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -0,0 +1,5 @@
export default (hub, details) => {
if (details) {
return hub.parseExpression(`(${details.value})=>{}`, details.pos).params;
}
};

View 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);
}
}

View 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;

View 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 };
}

File diff suppressed because it is too large Load Diff

View 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);
}

View 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" }
}

View 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" }
}

View 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() + "/", "");
}

View 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"
}
}

View 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);
}
}

View 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)}]]>`);
}

View 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)));
}

View 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();
}

Some files were not shown because too many files have changed in this diff Show More