Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Improved demo transpiling #15438

Merged
merged 18 commits into from
Apr 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:
- install_js
- run:
name: Transpile TypeScript demos
command: yarn docs:typescript:formatted
command: yarn docs:typescript:formatted --disable-cache
- run:
name: Are the compiled TypeScript demos equivalent to the JavaScript demos?
command: git add -A && git diff --exit-code --staged
Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,8 @@ in TypeScript.

Changing demos in JavaScript requires a manual update of the TypeScript
eps1lon marked this conversation as resolved.
Show resolved Hide resolved
version. If you are not familiar with this language you can add the filepath
of the TS demo to `docs/ts-demo-ignore.json`. See `docs/babel.config.ts.js` for more
information. Otherwise our CI will fail the `test_build` job.
A contributor can later update the TypeScript version of that demo.
of the TS demo to `docs/scripts/formattedTSDemos.js`. Otherwise our CI will fail the
`test_build` job. A contributor can later update the TypeScript version of that demo.

If you are already familiar with TypeScript you can simply write the demo in TypeScript.
`yarn docs:typescript:formatted` will transpile it down to JavaScript.
Expand Down
24 changes: 0 additions & 24 deletions docs/babel.config.ts.js

This file was deleted.

176 changes: 139 additions & 37 deletions docs/scripts/formattedTSDemos.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,157 @@
/* eslint-disable no-console */

/**
* Transpiles and formats TS demos.
* Transpiles TypeScript demos to formatted JavaScript.
* Can be used to verify that JS and TS demos are equivalent. No introduced change
* would indicate equivalence.
*/
const childProcess = require('child_process');

/**
* List of demos to ignore when transpiling
* Example: "app-bar/BottomAppBar.tsx"
*/
const ignoreList = [];
eps1lon marked this conversation as resolved.
Show resolved Hide resolved

const fse = require('fs-extra');
const path = require('path');
const babel = require('@babel/core');
const prettier = require('prettier');
const util = require('util');
const os = require('os');

const exec = util.promisify(childProcess.exec);
const babelConfig = {
presets: ['@babel/preset-typescript'],
plugins: ['unwrap-createStyles'],
generatorOpts: { retainLines: true },
babelrc: false,
configFile: false,
};

async function getUnstagedGitFiles() {
const { stdout } = await exec('git diff --name-only');
const list = stdout.trim();
const watchMode = process.argv.some(arg => arg === '--watch');
const cacheDisabled = process.argv.some(arg => arg === '--disable-cache');

if (list === '') {
// "".split(" ") => [""]
return [];
}
const workspaceRoot = path.join(__dirname, '../../');

const prettierConfig = prettier.resolveConfig.sync(process.cwd(), {
config: path.join(workspaceRoot, 'prettier.config.js'),
});

return list.split('\n');
async function getFiles(root) {
const files = [];

await Promise.all(
(await fse.readdir(root)).map(async name => {
const filePath = path.join(root, name);
const stat = await fse.stat(filePath);

if (stat.isDirectory()) {
files.push(...(await getFiles(filePath)));
} else if (
stat.isFile() &&
filePath.endsWith('.tsx') &&
!ignoreList.some(ignorePath => filePath.endsWith(path.normalize(ignorePath)))
) {
files.push(filePath);
}
}),
);

return files;
}

function getLineFeed(source) {
const match = source.match(/\r?\n/);
if (match === null) {
return os.EOL;
}
return match[0];
}

const fixBabelIssuesRegExp = new RegExp(/(?<=(\/>)|,)(\r?\n){2}/g);
function fixBabelGeneratorIssues(source) {
return source.replace(/\/>\n\n/g, '/>\n').replace(/,\n\n/g, ',\n');
return source.replace(fixBabelIssuesRegExp, getLineFeed(source));
}

exec('yarn docs:typescript')
.then(() => {
const prettierConfigPath = path.join(__dirname, '../../prettier.config.js');
const prettierConfig = prettier.resolveConfig(process.cwd(), { config: prettierConfigPath });

return Promise.all([getUnstagedGitFiles(), prettierConfig]);
})
.then(([changedDemos, prettierConfig]) =>
Promise.all(
changedDemos.map(filename => {
const filepath = path.join(process.cwd(), filename);

return fse.readFile(filepath).then(source => {
const prettified = prettier.format(source.toString(), { ...prettierConfig, filepath });
const formatted = fixBabelGeneratorIssues(prettified);

return fse.writeFile(filepath, formatted);
});
}),
),
)
.catch(err => {
// eslint-disable-next-line no-console
const TranspileResult = {
Success: 0,
Skipped: 1,
Failed: 2,
};

async function transpileFile(tsxPath, ignoreCache = false) {
const jsPath = tsxPath.replace('.tsx', '.js');
try {
if (!cacheDisabled && !ignoreCache && (await fse.exists(jsPath))) {
const [jsStat, tsxStat] = await Promise.all([fse.stat(jsPath), fse.stat(tsxPath)]);
if (jsStat.mtimeMs > tsxStat.mtimeMs) {
// JavaScript version is newer, skip transpiling
return TranspileResult.Skipped;
}
}

const { code } = await babel.transformFileAsync(tsxPath, babelConfig);
const prettified = prettier.format(code, { ...prettierConfig, filepath: tsxPath });
const formatted = fixBabelGeneratorIssues(prettified);

await fse.writeFile(jsPath, formatted);
return TranspileResult.Success;
} catch (err) {
console.error(err);
process.exit(1);
return TranspileResult.Failed;
}
}

(async () => {
const tsxFiles = await getFiles(path.join(workspaceRoot, 'docs/src/pages'));

let successful = 0;
let failed = 0;
let skipped = 0;
(await Promise.all(tsxFiles.map(file => transpileFile(file)))).forEach(result => {
switch (result) {
case TranspileResult.Success: {
successful += 1;
break;
}
case TranspileResult.Failed: {
failed += 1;
break;
}
case TranspileResult.Skipped: {
skipped += 1;
break;
}
default: {
throw new Error(`No handler for ${result}`);
}
}
});

console.log(
[
'------ Summary ------',
'%i demo(s) were successfully transpiled',
'%i demo(s) were skipped',
'%i demo(s) were unsuccessful',
].join('\n'),
successful,
skipped,
failed,
);

if (!watchMode) {
if (failed > 0) {
process.exit(1);
}
return;
}

tsxFiles.forEach(filePath => {
fse.watchFile(filePath, { interval: 500 }, async () => {
if ((await transpileFile(filePath, true)) === 0) {
console.log('Success - %s', filePath);
}
});
});

console.log('\nWatching for file changes...');
})();
1 change: 0 additions & 1 deletion docs/ts-demo-ignore.json

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"docs:size-why": "DOCS_STATS_ENABLED=true yarn docs:build",
"docs:start": "next start",
"docs:i18n": "cross-env BABEL_ENV=test babel-node ./docs/scripts/i18n.js",
"docs:typescript": "cross-env NODE_ENV=development babel docs/src --config-file ./docs/babel.config.ts.js --extensions .tsx --out-dir docs/src",
"docs:typescript": "node docs/scripts/formattedTSDemos --watch",
"docs:typescript:check": "tslint -p docs/tsconfig.json",
"docs:typescript:formatted": "node docs/scripts/formattedTSDemos",
"jsonlint": "yarn --silent jsonlint:files | xargs -n1 jsonlint -q -c && echo \"jsonlint: no lint errors\"",
Expand Down