diff --git a/TSDX_README.md b/TSDX_README.md deleted file mode 100644 index 7555d82..0000000 --- a/TSDX_README.md +++ /dev/null @@ -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. - - - -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). - - - -### `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. diff --git a/package-lock.json b/package-lock.json index 81e58e2..65d2156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index b14d2c8..7e4c44a 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/generate-report.ts b/src/bench-reporting/generate-report.ts similarity index 98% rename from src/generate-report.ts rename to src/bench-reporting/generate-report.ts index a4df58f..1b923d6 100644 --- a/src/generate-report.ts +++ b/src/bench-reporting/generate-report.ts @@ -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, diff --git a/src/reporting.ts b/src/bench-reporting/reporting.ts similarity index 97% rename from src/reporting.ts rename to src/bench-reporting/reporting.ts index 0a7d53d..07efd53 100644 --- a/src/reporting.ts +++ b/src/bench-reporting/reporting.ts @@ -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( diff --git a/src/visit.ts b/src/bench-reporting/visit.ts similarity index 87% rename from src/visit.ts rename to src/bench-reporting/visit.ts index 5878e75..4fda3b6 100644 --- a/src/visit.ts +++ b/src/bench-reporting/visit.ts @@ -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(); diff --git a/src/compile-testcases.ts b/src/compile-testcases.ts index d9d2b67..cca92b0 100644 --- a/src/compile-testcases.ts +++ b/src/compile-testcases.ts @@ -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(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 ); diff --git a/src/external-untyped-modules.d.ts b/src/external-untyped-modules.d.ts index 33cec4c..d552ae8 100644 --- a/src/external-untyped-modules.d.ts +++ b/src/external-untyped-modules.d.ts @@ -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' { diff --git a/src/index.ts b/src/index.ts index 8385964..cbc6026 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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] ') + .option( + '-e --exclude-transforms ', + '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)); diff --git a/src/transforms/correctVirtualDom.ts b/src/transforms/correctVirtualDom.ts index 490e691..219a64b 100644 --- a/src/transforms/correctVirtualDom.ts +++ b/src/transforms/correctVirtualDom.ts @@ -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 +// ); +// } diff --git a/src/types.ts b/src/types.ts index 49b6eca..7f6293f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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; }; diff --git a/tsconfig.json b/tsconfig.json index 4b5e2ce..6b10219 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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__" + ] }