WIP first version of CLI

This commit is contained in:
Simon 2020-08-11 21:21:10 -07:00
parent 44c22abeff
commit 1be17184af
12 changed files with 226 additions and 256 deletions

View File

@ -1,27 +0,0 @@
# TSDX Bootstrap
This project was bootstrapped with [TSDX](https://github.com/jaredpalmer/tsdx).
## Local Development
Below is a list of commands you will probably find useful.
### `npm start` or `yarn start`
Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for you convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab.
<img src="https://user-images.githubusercontent.com/4060187/52168303-574d3a00-26f6-11e9-9f3b-71dbec9ebfcb.gif" width="600" />
Your library will be rebuilt if you make edits.
### `npm run build` or `yarn build`
Bundles the package to the `dist` folder.
The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
<img src="https://user-images.githubusercontent.com/4060187/52168322-a98e5b00-26f6-11e9-8cf6-222d716b75ef.gif" width="600" />
### `npm test` or `yarn test`
Runs the test watcher (Jest) in an interactive mode.
By default, runs tests related to files changed since the last commit.

30
package-lock.json generated
View File

@ -1796,6 +1796,15 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/commander": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
"integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==",
"dev": true,
"requires": {
"commander": "*"
}
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@ -3131,10 +3140,9 @@
}
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.0.0.tgz",
"integrity": "sha512-s7EA+hDtTYNhuXkTlhqew4txMZVdszBmKWSPEMxGr8ru8JXR7bLUFIAtPhcSuFdJQ0ILMxnJi8GkQL0yvDy/YA=="
},
"commondir": {
"version": "1.0.1",
@ -9269,6 +9277,14 @@
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
}
}
}
}
@ -10188,6 +10204,12 @@
"source-map-support": "~0.5.12"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View File

@ -10,15 +10,13 @@
"engines": {
"node": ">=10"
},
"bin": {
"elm-optimize": "./dist/index.js"
},
"scripts": {
"start": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint",
"prepare": "tsdx build",
"run": "ts-node src/index.ts",
"testcases": "ts-node -T src/compile-testcases.ts",
"report": "ts-node -T src/generate-report.ts"
"prepublish": "tsc -p tsconfig.json",
"run": "ts-node -T src/index.ts testcases/bench/Main.elm --modernize",
"report": "ts-node -T src/bench-reporting/generate-report.ts"
},
"peerDependencies": {},
"husky": {
@ -34,6 +32,7 @@
"author": "",
"module": "dist/lib.esm.js",
"devDependencies": {
"@types/commander": "^2.12.2",
"chromedriver": "^84.0.1",
"husky": "^4.2.5",
"node-elm-compiler": "^5.0.4",
@ -41,12 +40,12 @@
"selenium-webdriver": "^4.0.0-alpha.7",
"terser": "^5.0.0",
"ts-node": "^8.10.2",
"tsdx": "^0.13.2",
"tslib": "^2.0.0",
"typescript": "^3.9.7",
"uglify-js": "^3.10.0"
},
"dependencies": {
"commander": "^6.0.0",
"tree-sitter": "^0.16.1",
"tree-sitter-elm": "^2.7.9",
"ts-union": "^2.2.1"

View File

@ -5,11 +5,10 @@ Compiles all the test cases and runs them via webdriver to summarize the results
*/
import { ObjectUpdate, Transforms, Browser, InlineLists } from './types';
import { ObjectUpdate, Transforms, Browser, InlineLists } from '../types';
import * as Reporting from './reporting';
const defaultOptions: Transforms = {
prepack: true,
replaceVDomNode: false,
variantShapes: true,
inlineNumberToString: true,
@ -23,7 +22,6 @@ const defaultOptions: Transforms = {
};
const test: Transforms = {
prepack: false,
replaceVDomNode: false,
variantShapes: false,
inlineNumberToString: false,

View File

@ -1,13 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as Compile from './compile-testcases';
import {
Browser,
Transforms,
ObjectUpdate,
RunTestcaseOptions,
InlineLists,
} from './types';
import * as Compile from '../compile-testcases';
import { Transforms, RunTestcaseOptions, InlineLists } from '../types';
import * as Visit from './visit';
export interface Stat {
@ -223,7 +217,6 @@ export const run = async function(
};
const emptyOpts: Transforms = {
prepack: false,
replaceVDomNode: false,
variantShapes: false,
inlineNumberToString: false,
@ -316,10 +309,10 @@ export const runWithBreakdown = async function(
let results: any[] = [];
let assets: any = {};
const opts = {
browser: Browser.Chrome,
headless: false,
};
// const opts = {
// browser: Browser.Chrome,
// headless: false,
// };
for (let instance of runnable) {
await Compile.compileAndTransform(

View File

@ -2,14 +2,16 @@ import * as Webdriver from 'selenium-webdriver';
import * as chrome from 'selenium-webdriver/chrome';
import * as firefox from 'selenium-webdriver/firefox';
import * as Path from 'path';
import { Browser, BrowserOptions } from './types'
export const benchmark = async (options: BrowserOptions, tag: string | null, file: string) => {
import { BrowserOptions } from '../types';
export const benchmark = async (
options: BrowserOptions,
tag: string | null,
file: string
) => {
const firefoxOptions = new firefox.Options();
const chromeOptions = new chrome.Options();
if (options.headless) {
firefoxOptions.headless();
chromeOptions.headless();

View File

@ -4,7 +4,7 @@ import * as path from 'path';
import { parseElm, parseDir, primitives } from './parseElm';
import ts from 'typescript';
import { createCustomTypesTransformer } from './transforms/variantShapes';
import { Mode, Transforms, ObjectUpdate, InlineLists } from './types';
import { Mode, Transforms, InlineLists } from './types';
import {
createFunctionInlineTransformer,
InlineContext,
@ -13,7 +13,7 @@ import {
InlineMode,
createInlineListFromArrayTransformer,
} from './transforms/inlineListFromArray';
import { prepackFileSync } from 'prepack';
// import { prepackFileSync } from 'prepack';
import * as Terser from 'terser';
import { execSync } from 'child_process';
import { inlineEquality } from './transforms/inlineEquality';
@ -27,7 +27,7 @@ import { createPassUnwrappedFunctionsTransformer } from './transforms/passUnwrap
import { replaceVDomNode } from './transforms/correctVirtualDom';
import { inlineNumberToString } from './transforms/inlineNumberToString';
type Options = {
export type Options = {
compile: boolean;
minify: boolean;
gzip: boolean;
@ -111,14 +111,16 @@ export const compileAndTransform = async (
transforms.passUnwrappedFunctions,
createPassUnwrappedFunctionsTransformer(() => inlineCtx),
],
includeObjectUpdate(transforms.objectUpdate),
[
!!transforms.objectUpdate,
transforms.objectUpdate && objectUpdate(transforms.objectUpdate),
],
[transforms.arrowFns, convertFunctionExpressionsToArrowFuncs],
[transforms.unusedValues, createRemoveUnusedLocalsTransform()],
]);
const {
transformed: [result],
diagnostics,
} = ts.transform(source, transformations);
const printer = ts.createPrinter();
@ -137,24 +139,25 @@ export const compileAndTransform = async (
fs.writeFileSync(pathInOutput('elm.opt.js'), printer.printFile(initialJs));
// Prepack, minify, and gzip
if (transforms.prepack) {
const { code } = prepackFileSync([pathInOutput('elm.opt.transformed.js')], {
debugNames: true,
inlineExpressions: true,
maxStackDepth: 1200, // that didn't help
});
// if (false) {
// // if (transforms.prepack) {
// const { code } = prepackFileSync([pathInOutput('elm.opt.transformed.js')], {
// debugNames: true,
// inlineExpressions: true,
// maxStackDepth: 1200, // that didn't help
// });
fs.writeFileSync(pathInOutput('elm.opt.prepack.js'), code);
if (options.minify) {
await minify(
pathInOutput('elm.opt.prepack.js'),
pathInOutput('elm.opt.prepack.min.js')
);
}
if (options.gzip) {
gzip(pathInOutput('elm.opt.prepack.min.js'));
}
}
// fs.writeFileSync(pathInOutput('elm.opt.prepack.js'), code);
// if (options.minify) {
// await minify(
// pathInOutput('elm.opt.prepack.js'),
// pathInOutput('elm.opt.prepack.min.js')
// );
// }
// if (options.gzip) {
// gzip(pathInOutput('elm.opt.prepack.min.js'));
// }
// }
if (options.minify) {
await minify(pathInOutput('elm.opt.js'), pathInOutput('elm.opt.min.js'));
@ -171,19 +174,11 @@ export const compileAndTransform = async (
return {};
};
function includeObjectUpdate(kind: ObjectUpdate | null): any {
if (kind != null) {
return [true, objectUpdate(kind)];
} else {
return [];
}
}
function removeDisabled(list: any[]) {
let newList: any[] = [];
list.forEach(item => {
if (item != 0 && item[0]) {
newList.push(item[1]);
function removeDisabled<T>(list: [null | boolean | undefined, T][]): T[] {
let newList: T[] = [];
list.forEach(([cond, val]) => {
if (![null, false, undefined].includes(cond)) {
newList.push(val);
}
});
@ -249,7 +244,7 @@ function reportInlineTransformResult(ctx: InlineContext) {
const { splits, partialApplications, inlined } = ctx;
console.log(
`functionInlineTransformer: splitCount=${splits.size}, partialApplicationCount=${partialApplications.size}`,
`functionInlineTransformer: splitCount=${splits.size}, partialApplicationCount=${partialApplications.size}, inlined=`,
inlined
// splits
);

View File

@ -1,4 +1,7 @@
declare module 'node-elm-compiler';
declare module 'node-elm-compiler' {
export function compileSync(files: string[], options: object): void;
}
declare module 'tree-sitter-elm';
declare module 'prepack' {

View File

@ -1,110 +1,88 @@
import ts from 'typescript';
// tslint:disable-next-line no-require-imports no-var-requires
import program from 'commander';
import * as path from 'path';
import { compileAndTransform } from './compile-testcases';
import { ObjectUpdate, Transforms, InlineLists } from './types';
const { version } = require('../package.json');
const filename = 'test.js';
const code = `
(function (){
function f () {}
const f2 = () => 1;
const test = 1 + 2;
console.log(test);
})()
`;
// (function() {
// function f() {}
// const f2 = () => 1;
// const test: number = 1 + 2;
// })();
const sourceFile = ts.createSourceFile(filename, code, ts.ScriptTarget.Latest);
const defaultCompilerHost = ts.createCompilerHost({});
const customCompilerHost: ts.CompilerHost = {
getSourceFile: (name, languageVersion) => {
console.log(`getSourceFile ${name}`);
if (name === filename) {
return sourceFile;
} else {
return defaultCompilerHost.getSourceFile(name, languageVersion);
}
},
writeFile: () => {},
getDefaultLibFileName: () =>
'node_modules/typescript/lib/lib.es2018.full.d.ts',
useCaseSensitiveFileNames: () => false,
getCanonicalFileName: filename => filename,
getCurrentDirectory: () => '',
getNewLine: () => '\n',
getDirectories: () => [],
fileExists: () => true,
readFile: () => '',
const defaultOptions: Transforms = {
replaceVDomNode: false,
variantShapes: true,
inlineNumberToString: true,
inlineEquality: true,
inlineFunctions: true,
listLiterals: InlineLists.AsObjects,
passUnwrappedFunctions: true,
arrowFns: false,
objectUpdate: ObjectUpdate.InlineSpread,
unusedValues: false, // do we want this by default?
};
const program = ts.createProgram(
[filename],
{ allowJs: true, noUnusedLocals: true, outDir: 'yo', checkJs: true },
customCompilerHost
);
program
.version(version)
.usage('[options] <src/Main.elm>')
.option(
'-e --exclude-transforms <excludedTransforms>',
'names of transforms that should be excluded (comma delimited). ' +
'Names of available transforms:' +
Object.keys(defaultOptions)
.map(name => `'${name}'`)
.join(', '),
v => v.split(','),
[]
)
.option(
'-m --modernize',
'transform into a more modern JS to save size (es2018)',
false
)
.parse(process.argv);
const diagnostics = ts.getPreEmitDiagnostics(program);
type CLIOptions = {
modernize: boolean;
excludeTransforms: string[];
};
for (const diagnostic of diagnostics) {
const message = diagnostic.messageText;
const file = diagnostic.file;
const filename = file!.fileName;
const lineAndChar = file!.getLineAndCharacterOfPosition(diagnostic!.start!);
const line = lineAndChar.line + 1;
const character = lineAndChar.character + 1;
console.log(message);
console.log(
`(${filename}:${line}:${character}), pos = (${
diagnostic?.start
},${(diagnostic?.start || 0) + (diagnostic?.length || 0)})`
);
}
console.log('--------A--------------');
const typeChecker = program.getTypeChecker();
function recursivelyPrintVariableDeclarations(
node: ts.Node,
sourceFile: ts.SourceFile
) {
if (ts.isVariableDeclaration(node)) {
const nodeText = node.getText(sourceFile);
const type = typeChecker.getTypeAtLocation(node);
const typeName = typeChecker.typeToString(type, node);
console.log(nodeText);
console.log(`(${typeName})`);
async function run(filePath: string | undefined, options: CLIOptions) {
if (!filePath || !filePath.endsWith('.elm')) {
console.error('Please provide a path to an Elm file.');
program.outputHelp();
return;
}
node.forEachChild(child =>
recursivelyPrintVariableDeclarations(child, sourceFile)
const { excludeTransforms, modernize } = options;
excludeTransforms;
modernize;
const dirname = path.dirname(filePath);
const fileName = path.basename(filePath);
const withExcluded: Transforms = Object.fromEntries(
Object.entries(defaultOptions).map(([name, val]) =>
excludeTransforms.includes(name) ? [name, false] : [name, val]
)
) as any;
const withCorrections = {
...withExcluded,
arrowFns: modernize && withExcluded.arrowFns,
objectUpdate: modernize && withExcluded.objectUpdate,
passUnwrappedFunctions:
withExcluded.inlineFunctions && withExcluded.passUnwrappedFunctions,
};
await compileAndTransform(
dirname,
fileName,
{
compile: true,
gzip: false,
minify: false,
},
withCorrections
);
// console.log({ filePath, options });
}
recursivelyPrintVariableDeclarations(sourceFile, sourceFile);
console.log('--------B--------------');
function printRecursiveFrom(
node: ts.Node,
indentLevel: number,
sourceFile: ts.SourceFile
) {
const indentation = '-'.repeat(indentLevel);
const syntaxKind = ts.SyntaxKind[node.kind];
const nodeText = node.getText(sourceFile);
console.log(`${indentation}${syntaxKind}: ${nodeText}`);
console.log(`pos: (${node.pos}, ${node.end})`);
node.forEachChild(child =>
printRecursiveFrom(child, indentLevel + 1, sourceFile)
);
}
printRecursiveFrom(sourceFile, 0, sourceFile);
run(program.args[0], program.opts() as any).catch(e => console.error(e));

View File

@ -92,9 +92,11 @@ function replaceVDomWithNSInline(node: ts.Node): ts.Node | undefined {
ts.isIdentifier(node.expression) &&
node.expression.text === 'A3'
) {
const [firstArg] = node.arguments;
if (
ts.isIdentifier(node.arguments[0]) &&
node.arguments[0].text === '$elm$virtual_dom$VirtualDom$node'
ts.isIdentifier(firstArg) &&
firstArg.text === '$elm$virtual_dom$VirtualDom$node'
) {
return ts.createCall(ts.createIdentifier('A4'), undefined, [
ts.createIdentifier('_VirtualDom_nodeNS'),
@ -117,52 +119,54 @@ function replaceVDomWithNSInline(node: ts.Node): ts.Node | undefined {
// };
// with
// var $elm$virtual_dom$VirtualDom$node = _VirtualDom_nodeNS(undefined, "div");
function replaceAPIVDomNodeWithF3A4(node: ts.Node): ts.Node | undefined {
if (
ts.isVariableDeclaration(node) &&
ts.isIdentifier(node.name) &&
node.name.text == '$elm$virtual_dom$VirtualDom$node' &&
node.initializer &&
ts.isFunctionExpression(node.initializer)
) {
let newCall = ts.createCall(ts.createIdentifier('A4'), undefined, [
ts.createIdentifier('_VirtualDom_nodeNS'),
ts.createIdentifier('undefined'),
ts.createCall(ts.createIdentifier('_VirtualDom_noScript'), undefined, [
ts.createIdentifier('tag'),
]),
ts.createIdentifier('attrs'),
ts.createIdentifier('kids'),
]);
// function replaceAPIVDomNodeWithF3A4(node: ts.Node): ts.Node | undefined {
// if (
// ts.isVariableDeclaration(node) &&
// ts.isIdentifier(node.name) &&
// node.name.text == '$elm$virtual_dom$VirtualDom$node' &&
// node.initializer &&
// ts.isFunctionExpression(node.initializer)
// ) {
// let newCall = ts.createCall(ts.createIdentifier('A4'), undefined, [
// ts.createIdentifier('_VirtualDom_nodeNS'),
// ts.createIdentifier('undefined'),
// ts.createCall(ts.createIdentifier('_VirtualDom_noScript'), undefined, [
// ts.createIdentifier('tag'),
// ]),
// ts.createIdentifier('attrs'),
// ts.createIdentifier('kids'),
// ]);
let newFn = ts.createCall(ts.createIdentifier('F3'), undefined, [
ts.createFunctionExpression(
undefined,
undefined,
undefined,
undefined,
[param('tag'), param('attrs'), param('kids')],
undefined,
ts.createBlock([ts.createReturn(newCall)])
),
]);
// let newFn = ts.createCall(ts.createIdentifier('F3'), undefined, [
// ts.createFunctionExpression(
// undefined,
// undefined,
// undefined,
// undefined,
// [param('tag'), param('attrs'), param('kids')],
// undefined,
// ts.createBlock([ts.createReturn(newCall)])
// ),
// ]);
return ts.createVariableDeclaration(
'$elm$virtual_dom$VirtualDom$node',
undefined,
newFn
);
}
}
// return ts.createVariableDeclaration(
// '$elm$virtual_dom$VirtualDom$node',
// undefined,
// newFn
// );
// }
function param(name: string) {
return ts.createParameter(
undefined,
undefined,
undefined,
ts.createIdentifier(name),
undefined,
undefined,
undefined
);
}
// return undefined;
// }
// function param(name: string) {
// return ts.createParameter(
// undefined,
// undefined,
// undefined,
// ts.createIdentifier(name),
// undefined,
// undefined,
// undefined
// );
// }

View File

@ -22,16 +22,15 @@ export type RunTestcaseOptions = {
};
export type Transforms = {
prepack: boolean;
replaceVDomNode: boolean;
variantShapes: boolean;
inlineNumberToString: boolean;
inlineEquality: boolean;
inlineFunctions: boolean;
passUnwrappedFunctions: boolean;
listLiterals: InlineLists | null;
listLiterals: InlineLists | null | false;
arrowFns: boolean;
objectUpdate: ObjectUpdate | null;
objectUpdate: ObjectUpdate | null | false;
unusedValues: boolean;
};

View File

@ -1,13 +1,13 @@
{
"include": ["src", "types"],
"compilerOptions": {
"target": "es2018",
"module": "CommonJS",
"lib": ["dom", "esnext"],
"importHelpers": true,
"lib": ["es6", "esnext"],
"importHelpers": false,
"declaration": true,
"sourceMap": true,
"rootDir": "./src",
"outDir": "dist",
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
@ -16,10 +16,14 @@
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"*": ["src/*", "node_modules/*"]
},
"jsx": "react",
"esModuleInterop": true
}
},
"include": ["src"],
"exclude": [
"node_modules",
"src/bench-reporting",
"__tests__",
"**/__tests__",
"src/__tests__"
]
}