react-ecmascript
Version:
A script which transform React and ReactDOM into native Ecmascript modules to be used in browsers which support Ecmascript modules and module loading
113 lines (92 loc) • 3.92 kB
JavaScript
const fs = require('fs');
const { join, extname, dirname } = require('path');
const { promisify } = require('util');
const recast = require('recast');
const parser = require('recast/parsers/acorn');
const walk = require('./walk');
const read = promisify(fs.readFile);
const readDirectory = promisify(fs.readdir);
const write = promisify(fs.writeFile);
const baseDirectory = [__dirname];
const transformersDirectory = baseDirectory.concat('transformers');
const targetsDirectory = baseDirectory.concat('targets');
const joiner = p => join.apply(null, p);
const shouldWriteLibrariesToDisk = require.main === module;
const exportedProperties = {
react: Object.keys(require('react')),
'react-dom': Object.keys(require('react-dom'))
};
// assuming the verions of react and react-dom stay the same we get the version from the package.json
const version = process.env.npm_package_dependencies_react.replace(/[\^><=~]/g, '');
function getModuleStrings(importSourceUri = './') {
return readDirectory(joiner(transformersDirectory))
.then(filenames =>
Promise.all(
filenames.map(filename => {
const {
targetSelectSteps,
sourceModule,
sourceFile,
sourceSelectSteps,
importSelectSteps,
importFilename
} = require(joiner(transformersDirectory.concat(filename)));
const resolvedSourceFile = joiner(
[dirname(require.resolve(sourceModule))].concat(sourceFile)
);
return read(joiner(targetsDirectory.concat(filename)), 'utf8').then(targetContent => {
targetContent = targetContent
.replace(/@@exports@@/g, exportedProperties[sourceModule].join(','))
.replace(/@@version@@/g, version);
const targtetAst = recast.parse(targetContent, { parser });
return read(resolvedSourceFile)
.then(sourceContent => {
const newFilename = filename.replace(extname(filename), '.mjs');
const sourceAst = recast.parse(sourceContent, { parser });
// reference the part in the source AST we want to use
const functionExpression = walk(sourceAst.program, sourceSelectSteps);
// select the part we want to replace in the target with the part of the source AST
// and copy the functionExpression over to it
walk(targtetAst.program, targetSelectSteps).callee = functionExpression;
if (importSelectSteps && importFilename) {
const finalImportSourceUri = `${importSourceUri}${importFilename}`;
walk(targtetAst.program, importSelectSteps).value = finalImportSourceUri;
console.log(
`replaced import source in ${newFilename} to ${finalImportSourceUri}`
);
}
const generated = recast.print(targtetAst, { parser }).code;
return {
filename: newFilename,
content: generated
};
})
.catch(err => {
console.error(err);
});
});
})
)
)
.then(generated =>
generated.reduce(
(prev, { filename, content }) => Object.assign(prev, { [filename]: content }),
{}
)
)
.catch(console.error);
}
if (shouldWriteLibrariesToDisk) {
getModuleStrings().then(generated =>
Object.keys(generated).map(filename =>
write(join(__dirname, '..', filename), generated[filename], 'utf8').then(() =>
console.log(`written /${filename}`)
)
)
);
}
// export the function to enable external scripts to call this module and get an object returned with the ECMAScript modules as strings
module.exports = function(uri) {
return getModuleStrings(uri);
};