Skip to content
This repository has been archived by the owner on May 17, 2019. It is now read-only.

Commit

Permalink
Make translations discovery caching robust
Browse files Browse the repository at this point in the history
  • Loading branch information
rtsao committed Aug 30, 2018
1 parent 0483dd5 commit ff504c9
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 160 deletions.
107 changes: 46 additions & 61 deletions build/babel-plugins/babel-plugin-i18n/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@

/* eslint-env node */

const i18nManifest = require('../../i18n-manifest.js');
const createModuleVisitor = require('../babel-plugin-utils/visit-named-module');

const PACKAGE_NAME = ['fusion-plugin-i18n-react', 'fusion-plugin-i18n-preact'];
const COMPONENT_IDENTIFIER = ['Translate', 'withTranslations'];
const TRANSLATIONS_KEY = Symbol('@uber/babel-plugin-i18n translation state');

module.exports = i18nPlugin;

function i18nPlugin(babel /*: Object */) {
/*::
type PluginOpts = {translationIds: Set<string>}
*/

function i18nPlugin(babel /*: Object */, {translationIds} /*: PluginOpts */) {
const t /*: Object */ = babel.types;
const visitor = createModuleVisitor(
t,
Expand All @@ -26,69 +28,52 @@ function i18nPlugin(babel /*: Object */) {
refsHandler
);

const programVisitor = {
Program: {
enter(path, state) {
Object.defineProperty(state, TRANSLATIONS_KEY, {
value: new Set(),
});
},

exit(path, state) {
const translations = state[TRANSLATIONS_KEY];
if (translations.size > 0) {
i18nManifest.set(this.file.opts.filename, translations);
}
},
},
};

return {visitor: Object.assign({}, visitor, programVisitor)};
}

function refsHandler(t, context, refs = [], specifierName) {
refs.forEach(refPath => {
if (t.isCallExpression(refPath.parent)) {
const firstArg = refPath.parent.arguments[0];
if (specifierName === 'withTranslations') {
const errorMessage =
'The withTranslations hoc must be called with an array of string literal translation keys';
if (!t.isArrayExpression(firstArg)) {
throw new Error(errorMessage);
}
const elements = firstArg.elements;
elements.forEach(element => {
if (!t.isStringLiteral(element)) {
function refsHandler(t, context, refs = [], specifierName) {
refs.forEach(refPath => {
if (t.isCallExpression(refPath.parent)) {
const firstArg = refPath.parent.arguments[0];
if (specifierName === 'withTranslations') {
const errorMessage =
'The withTranslations hoc must be called with an array of string literal translation keys';
if (!t.isArrayExpression(firstArg)) {
throw new Error(errorMessage);
}
context[TRANSLATIONS_KEY].add(element.value);
});
}
return;
}
if (!t.isJSXOpeningElement(refPath.parent)) {
return;
}
refPath.parent.attributes.forEach(attr => {
if (!t.isJSXAttribute(attr)) {
return;
}
if (!t.isJSXIdentifier(attr.name)) {
return;
}
if (!t.isStringLiteral(attr.value)) {
const elements = firstArg.elements;
elements.forEach(element => {
if (!t.isStringLiteral(element)) {
throw new Error(errorMessage);
}
translationIds.add(element.value);
});
}
return;
}
if (attr.name.name !== 'id') {
if (!t.isJSXOpeningElement(refPath.parent)) {
return;
}
if (!t.isStringLiteral(attr.value)) {
throw new Error(
'The translate component must have props.id be a string literal.'
);
}
const translationKeyId = attr.value.value;
context[TRANSLATIONS_KEY].add(translationKeyId);
refPath.parent.attributes.forEach(attr => {
if (!t.isJSXAttribute(attr)) {
return;
}
if (!t.isJSXIdentifier(attr.name)) {
return;
}
if (!t.isStringLiteral(attr.value)) {
return;
}
if (attr.name.name !== 'id') {
return;
}
if (!t.isStringLiteral(attr.value)) {
throw new Error(
'The translate component must have props.id be a string literal.'
);
}
const translationKeyId = attr.value.value;
translationIds.add(translationKeyId);
});
});
});
}

return {visitor};
}
38 changes: 5 additions & 33 deletions build/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const resolveFrom = require('resolve-from');

const getBabelConfig = require('./get-babel-config.js');
const LoaderContextProviderPlugin = require('./plugins/loader-context-provider-plugin.js');
const {chunkIdsLoader, fileLoader} = require('./loaders/index.js');
const {chunkIdsLoader, fileLoader, babelLoader} = require('./loaders/index.js');
const {DeferredState} = require('./shared-state-containers.js');
const {clientChunkIndexContextKey} = require('./loaders/loader-context.js');
const ChunkIndexStateHydratorPlugin = require('./plugins/chunk-index-state-hydrator-plugin');
Expand Down Expand Up @@ -192,9 +192,8 @@ function getConfig({target, env, dir, watch, state}) {
],
use: [
{
loader: require.resolve('babel-loader'),
loader: babelLoader.path,
options: {
cacheDirectory: `${dir}/node_modules/.fusion_babel_cache`,
...getBabelConfig({
runtime:
target === 'node' ? 'node-bundled' : 'browser-legacy',
Expand Down Expand Up @@ -222,34 +221,13 @@ function getConfig({target, env, dir, watch, state}) {
],
...getBabelConfig({
dev: env === 'development',
transformGlobals: true,
jsx: {pragma},
fusionTransforms: true,
assumeNoImportSideEffects:
fusionConfig.assumeNoImportSideEffects,
runtime:
target === 'node' ? 'node-bundled' : 'browser-legacy',
specOnly: false,
plugins: [
//cup-globals works with webpack.EnvironmentPlugin(['NODE_ENV']) to implement static conditionals
require.resolve(
'./babel-plugins/babel-plugin-asseturl'
),
require.resolve(
'./babel-plugins/babel-plugin-pure-create-plugin'
),
require.resolve(
'./babel-plugins/babel-plugin-sync-chunk-ids'
),
require.resolve(
'./babel-plugins/babel-plugin-sync-chunk-paths'
),
require.resolve('./babel-plugins/babel-plugin-chunkid'),
pragma && [
require.resolve('@babel/plugin-transform-react-jsx'),
{pragma},
],
target === 'web' &&
require.resolve('./babel-plugins/babel-plugin-i18n'),
].filter(Boolean),
}),
},
],
Expand Down Expand Up @@ -358,13 +336,7 @@ function getConfig({target, env, dir, watch, state}) {
new webpack.HashedModuleIdsPlugin(),
target === 'web' && new SyncChunkIdsPlugin(),
target === 'web' && new ClientChunkBundleUrlMapPlugin(['es5'], 'es5'),
target === 'web' &&
new I18nDiscoveryPlugin({
cachePath: path.join(
dir,
'node_modules/.fusion_babel_cache/i18n-manifest.json'
),
}),
target === 'web' && new I18nDiscoveryPlugin(),
// case-insensitive paths can cause problems
new CaseSensitivePathsPlugin(),
target === 'node' &&
Expand Down
24 changes: 12 additions & 12 deletions build/get-babel-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type BabelConfigOpts =
presets?: Array<any>,
jsx?: JSXTransformOpts,
assumeNoImportSideEffects?: boolean,
transformGlobals: boolean
fusionTransforms: boolean
|};
*/

Expand All @@ -48,7 +48,7 @@ module.exports = function getBabelConfig(opts /*: BabelConfigOpts */) {
};

if (opts.specOnly === false) {
let {jsx, assumeNoImportSideEffects, dev, transformGlobals} = opts;
let {jsx, assumeNoImportSideEffects, dev, fusionTransforms} = opts;
if (!jsx) {
jsx = {};
}
Expand All @@ -61,10 +61,9 @@ module.exports = function getBabelConfig(opts /*: BabelConfigOpts */) {
},
]);
config.presets.push(require('@babel/preset-flow'));
config.presets.push([
fusionPreset,
{runtime, assumeNoImportSideEffects, transformGlobals},
]);
if (fusionTransforms) {
config.presets.push([fusionPreset, {runtime, assumeNoImportSideEffects}]);
}
}

if (runtime === 'node-native') {
Expand Down Expand Up @@ -112,7 +111,6 @@ module.exports = function getBabelConfig(opts /*: BabelConfigOpts */) {
type FusionPresetOpts = {
runtime: Runtime,
assumeNoImportSideEffects: boolean,
transformGlobals: boolean,
};
*/

Expand All @@ -125,7 +123,7 @@ type FusionPresetOpts = {
*/
function fusionPreset(
context /*: any */,
{runtime, transformGlobals, assumeNoImportSideEffects} /*: FusionPresetOpts */
{runtime, assumeNoImportSideEffects} /*: FusionPresetOpts */
) {
const target =
runtime === 'node-native' || runtime === 'node-bundled'
Expand All @@ -134,10 +132,12 @@ function fusionPreset(

return {
plugins: [
transformGlobals && [
require('babel-plugin-transform-cup-globals'),
{target},
],
require('./babel-plugins/babel-plugin-asseturl'),
require('./babel-plugins/babel-plugin-pure-create-plugin'),
require('./babel-plugins/babel-plugin-sync-chunk-ids'),
require('./babel-plugins/babel-plugin-sync-chunk-paths'),
require('./babel-plugins/babel-plugin-chunkid'),
[require('babel-plugin-transform-cup-globals'), {target}],
assumeNoImportSideEffects && [
require('./babel-plugins/babel-plugin-transform-tree-shake'),
{target},
Expand Down
42 changes: 0 additions & 42 deletions build/i18n-discovery-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,19 @@

/* eslint-env node */

const fs = require('fs');

const manifest = require('./i18n-manifest.js');
const emitter = require('./i18n-manifest-emitter.js');

class I18nDiscoveryPlugin {
/*:: cachePath: string; */

constructor(opts /*: {cachePath: string} */) {
this.cachePath = opts.cachePath;
// On initial build, hydrate translations from previous build
// if valid cache exists
if (fs.existsSync(this.cachePath)) {
try {
let cached = JSON.parse(fs.readFileSync(this.cachePath, 'utf8'));
hydrate(cached);
} catch (e) {
// Do nothing
}
}
}
apply(compiler /*: any */) {
compiler.hooks.done.tap('I18nDiscoveryPlugin', () => {
try {
fs.writeFileSync(this.cachePath, serialize());
} catch (e) {
// Do nothing
}
emitter.set(manifest);
});
compiler.hooks.invalid.tap('I18nDiscoveryPlugin', filename => {
try {
fs.unlinkSync(this.cachePath);
} catch (e) {
// Do nothing
}
emitter.invalidate();
manifest.delete(filename);
});
}
}

module.exports = I18nDiscoveryPlugin;

function serialize() {
const json = {};
for (let [file, translations] of manifest.entries()) {
json[file] = Array.from(translations);
}
return JSON.stringify(json);
}

function hydrate(parsed) {
Object.keys(parsed).forEach(file => {
const translations = new Set(parsed[file]);
manifest.set(file, translations);
});
}
2 changes: 1 addition & 1 deletion build/jest/jest-transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const babelConfig = getBabelConfig({
plugins: customPlugins,
presets: customPresets,
dev: false,
transformGlobals: false,
fusionTransforms: false,
});

const transformer = require('babel-jest').createTransformer(babelConfig);
Expand Down
Loading

0 comments on commit ff504c9

Please sign in to comment.