diff --git a/src/helpers.js b/src/helpers.js index e8cfa4f..bca6723 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,30 +1,11 @@ import path from 'path' +import fs from 'fs' /** * Formats an output file, which re-exports items from a list of paths. - * @param {*} config the rollup-plugin-flow-entry config object. - * @param {string} outDir the output directory. - * @param {string} fileName the output file name. - * @param {string[]} paths an array of absolute paths to export types from. */ -export function buildEntry(config, outDir, fileName, paths) { - const { mode, types } = config - - // Handle path overrides: - if (typeof types === 'string') { - paths = [types] - } else if (Array.isArray(types)) { - paths = types - } else if (typeof types === 'object' && types != null) { - const ourTypes = types[fileName] - if (typeof ourTypes === 'string') { - paths = [ourTypes] - } else if (Array.isArray(ourTypes)) { - paths = ourTypes - } else if (ourTypes === false) { - return - } - } +export function buildAsset(outDir, flowEntry, mode) { + const { fileName, input } = flowEntry // Set up the path resolution logic: const here = path.dirname(path.resolve(outDir, fileName)) @@ -38,11 +19,27 @@ export function buildEntry(config, outDir, fileName, paths) { // Build the source code: let source = mode != null ? `// @flow ${mode}\n\n` : '// @flow\n\n' - for (let i = 0; i < paths.length; ++i) { - source += `export * from '${escapePath(paths[i])}'\n` + if (typeof input === 'string') { + const sourceText = fs.readFileSync(input, 'utf8') + if (hasDefaultExport(sourceText)) { + source += `export { default } from '${escapePath(input)}'\n` + } + source += `export * from '${escapePath(input)}'\n` + } else { + for (let i = 0; i < input.length; ++i) { + source += `export * from '${escapePath(input[i])}'\n` + } } - return { fileName: fileName + '.flow', isAsset: true, source } + return { fileName, isAsset: true, source } +} + +/** + * Returns true if the source code contains "export default". + */ +function hasDefaultExport(sourceText) { + // TODO: Use a proper AST + return /export\s+default/.test(sourceText) } /** diff --git a/src/index.js b/src/index.js index 7440fe0..c10b9d3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import path from 'path' -import { buildEntry, parseMultiEntry } from './helpers.js' +import { parseMultiEntry, buildAsset } from './helpers.js' const multiEntryId = '\0rollup-plugin-multi-entry:entry-point' @@ -18,35 +18,57 @@ export default function flowEntry(config = {}) { generateBundle(opts, bundle) { const outDir = opts.dir != null ? opts.dir : path.dirname(opts.file) + // A list of Flow entries we need to generate: + const entries = [] + function pushFlowEntry(fileName, input) { + const { types } = config + if (typeof types === 'string') { + input = path.resolve(types) + } else if (Array.isArray(types)) { + input = types.map(p => path.resolve(p)) + } else if (typeof types === 'object' && types != null) { + const ourTypes = types[fileName] + if (typeof ourTypes === 'string') { + input = path.resolve(ourTypes) + } else if (Array.isArray(ourTypes)) { + input = ourTypes.map(p => path.resolve(p)) + } else if (ourTypes === false) { + return + } + } + entries.push({ fileName: fileName + '.flow', input }) + } + + // Find the bundle outputs that need flow entries: for (const n in bundle) { const file = bundle[n] if (file.isAsset || !file.isEntry || file.facadeModuleId == null) { continue } - if (file.facadeModuleId !== multiEntryId) { - // Normal files: - const entry = buildEntry(config, outDir, file.fileName, [ - file.facadeModuleId - ]) - if (entry != null) bundle[entry.fileName] = entry - } else { - // rollup-plugin-multi-entry: + // rollup-plugin-multi-entry: + if (file.facadeModuleId === multiEntryId) { if (savedMultiEntry == null || opts.file == null) { this.warn( 'Unable to create Flow entry: rollup-plugin-multi-entry not configured correctly' ) continue } - - const entry = buildEntry( - config, - outDir, + pushFlowEntry( path.basename(opts.file), parseMultiEntry(outDir, savedMultiEntry) ) - if (entry != null) bundle[entry.fileName] = entry + continue } + + // Normal files: + pushFlowEntry(file.fileName, file.facadeModuleId) + } + + // Generate the entries: + for (const entry of entries) { + const asset = buildAsset(outDir, entry, config.mode) + bundle[asset.fileName] = asset } } } diff --git a/test/demo/types/entry.js.flow b/test/demo/types/entry.js.flow new file mode 100644 index 0000000..72a2beb --- /dev/null +++ b/test/demo/types/entry.js.flow @@ -0,0 +1 @@ +declare export default 'some string' diff --git a/test/index.test.js b/test/index.test.js index 8e5ac46..0bc0726 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -5,7 +5,7 @@ import { rollup } from 'rollup' import babel from 'rollup-plugin-babel' import multiEntry from 'rollup-plugin-multi-entry' -import { buildEntry } from '../src/helpers.js' +import { buildAsset } from '../src/helpers.js' import flowEntry from '../src/index.js' const babelOpts = { @@ -99,7 +99,7 @@ describe('rollup-plugin-flow-entry', function() { return rollup({ input: ['test/demo/entry1.js', 'test/demo/entry2.js'], plugins: [ - flowEntry({ types: 'test/types/entry.js.flow' }), + flowEntry({ types: 'test/demo/types/entry.js.flow' }), babel(babelOpts) ] }) @@ -109,12 +109,14 @@ describe('rollup-plugin-flow-entry', function() { expect(output).to.deep.include({ fileName: 'entry1.js.flow', isAsset: true, - source: "// @flow\n\nexport * from '../types/entry.js.flow'\n" + source: + "// @flow\n\nexport { default } from '../demo/types/entry.js.flow'\nexport * from '../demo/types/entry.js.flow'\n" }) expect(output).to.deep.include({ fileName: 'entry2.js.flow', isAsset: true, - source: "// @flow\n\nexport * from '../types/entry.js.flow'\n" + source: + "// @flow\n\nexport { default } from '../demo/types/entry.js.flow'\nexport * from '../demo/types/entry.js.flow'\n" }) }) }) @@ -125,7 +127,7 @@ describe('rollup-plugin-flow-entry', function() { plugins: [ flowEntry({ types: { - 'entry1.js': 'test/types/entry.js.flow', + 'entry1.js': 'test/demo/types/entry.js.flow', 'entry2.js': false, 'entry3.js': 'test/types/imaginary.js' } @@ -139,7 +141,8 @@ describe('rollup-plugin-flow-entry', function() { expect(output).to.deep.include({ fileName: 'entry1.js.flow', isAsset: true, - source: "// @flow\n\nexport * from '../types/entry.js.flow'\n" + source: + "// @flow\n\nexport { default } from '../demo/types/entry.js.flow'\nexport * from '../demo/types/entry.js.flow'\n" }) }) }) @@ -193,7 +196,16 @@ describe('rollup-plugin-flow-entry', function() { describe('buildEntry', function() { it('handles difficult paths', function() { - const expected = { + const entry = { + fileName: 'sub/index.js.flow', + input: [ + '/home/someone/sub/bare.js', + '/home/someone/windows\\style.js', + "/home/some'quotes'in/here.js" + ] + } + + expect(buildAsset('/home/someone', entry, 'semi-strict')).deep.equals({ fileName: 'sub/index.js.flow', isAsset: true, source: @@ -201,21 +213,6 @@ describe('buildEntry', function() { "export * from './bare.js'\n" + "export * from '../windows/style.js'\n" + "export * from '../../some\\'quotes\\'in/here.js'\n" - } - - const paths = [ - '/home/someone/sub/bare.js', - '/home/someone/windows\\style.js', - "/home/some'quotes'in/here.js" - ] - - expect( - buildEntry( - { mode: 'semi-strict' }, - '/home/someone', - 'sub/index.js', - paths - ) - ).deep.equals(expected) + }) }) })