diff --git a/package.json b/package.json index 274a50ea..be45b00e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "inflection": "1.10.0", "knex": "0.12.6", "ora": "0.4.1", - "rollup": "0.37.0", + "rollup": "0.41.1", "rollup-plugin-alias": "1.2.0", "rollup-plugin-babel": "2.7.1", "rollup-plugin-eslint": "3.0.0", diff --git a/scripts/build/config/index.js b/scripts/build/config/index.js index e51af63f..2e6e35e7 100644 --- a/scripts/build/config/index.js +++ b/scripts/build/config/index.js @@ -2,15 +2,14 @@ require('../../../lib/babel-hook'); -const fs = require('fs'); const path = require('path'); -// Plugins const json = require('rollup-plugin-json'); const babel = require('rollup-plugin-babel'); -const nodeResolve = require('rollup-plugin-node-resolve'); +const resolve = require('rollup-plugin-node-resolve'); -const template = require('../../../src/packages/template').default; +const { onwarn } = require('../../../src/packages/compiler'); +const { default: template } = require('../../../src/packages/template'); const BANNER = template` 'use strict'; @@ -22,20 +21,26 @@ const BANNER = template` module.exports = { rollup: { - external: [ - 'knex', - 'bundle', - path.join(__dirname, '..', '..', '..', 'lib', 'fs', 'index.js'), - ...fs.readdirSync(path.join(__dirname, '..', '..', '..', 'node_modules')) - ], - + onwarn, plugins: [ json(), babel(), - nodeResolve({ preferBuiltins: true }) - ] + resolve({ + preferBuiltins: true + }) + ], + external(id) { + return !( + id.startsWith('.') + || id.startsWith('/') // Absolute path on Unix + || /^[A-Z]:[\\/]/.test(id) // Absolute path on Windows + || id.startsWith('src') + || id.startsWith(path.join('..', '..', '..', 'src')) + || id === 'babelHelpers' + || id === '\u0000babelHelpers' + ); + }, }, - bundle: { banner: BANNER, format: 'cjs', diff --git a/src/constants.js b/src/constants.js index a5c06ee7..cf999131 100644 --- a/src/constants.js +++ b/src/constants.js @@ -21,6 +21,5 @@ export const NODE_ENV = ENV.NODE_ENV || 'development'; export const DATABASE_URL = ENV.DATABASE_URL; export const LUX_CONSOLE = ENV.LUX_CONSOLE || false; export const PLATFORM = platform(); -export const BACKSLASH = /\\/g; export const CIRCLECI = ENV.CIRCLECI; export const APPVEYOR = ENV.APPVEYOR; diff --git a/src/packages/application/utils/create-controller.js b/src/packages/application/utils/create-controller.js index 59cc6e3a..4e7a46e2 100644 --- a/src/packages/application/utils/create-controller.js +++ b/src/packages/application/utils/create-controller.js @@ -1,10 +1,8 @@ // @flow +import { posix } from 'path'; + import { deepFreezeProps } from '../../freezeable'; -import { - getNamespaceKey, - stripNamespaces, - closestAncestor -} from '../../loader'; +import { closestAncestor } from '../../loader'; import { tryCatchSync } from '../../../utils/try-catch'; import type Database from '../../database'; import type Controller from '../../controller'; @@ -21,9 +19,9 @@ export default function createController( } ): T { const { key, store, serializers } = opts; - const namespace = getNamespaceKey(key).replace('root', ''); + const namespace = posix.dirname(key).replace('.', ''); let { parent } = opts; - let model = tryCatchSync(() => store.modelFor(stripNamespaces(key))); + let model = tryCatchSync(() => store.modelFor(posix.basename(key))); let serializer = serializers.get(key); if (!model) { diff --git a/src/packages/application/utils/create-serializer.js b/src/packages/application/utils/create-serializer.js index 19b6c06d..d5d1e500 100644 --- a/src/packages/application/utils/create-serializer.js +++ b/src/packages/application/utils/create-serializer.js @@ -1,6 +1,7 @@ // @flow +import { posix } from 'path'; + import { deepFreezeProps } from '../../freezeable'; -import { getNamespaceKey, stripNamespaces } from '../../loader'; import { tryCatchSync } from '../../../utils/try-catch'; import type Serializer from '../../serializer'; // eslint-disable-line max-len, no-unused-vars import type { Application$factoryOpts } from '../index'; @@ -10,9 +11,9 @@ export default function createSerializer>( opts: Application$factoryOpts ): T { const { key, store } = opts; - const namespace = getNamespaceKey(key).replace('root', ''); + const namespace = posix.dirname(key).replace('.', ''); let { parent } = opts; - let model = tryCatchSync(() => store.modelFor(stripNamespaces(key))); + let model = tryCatchSync(() => store.modelFor(posix.basename(key))); if (!model) { model = null; diff --git a/src/packages/cli/generator/utils/generate-type.js b/src/packages/cli/generator/utils/generate-type.js index 5159a1d5..6fdec9a3 100644 --- a/src/packages/cli/generator/utils/generate-type.js +++ b/src/packages/cli/generator/utils/generate-type.js @@ -1,11 +1,10 @@ // @flow -import { join as joinPath } from 'path'; +import { posix, join as joinPath } from 'path'; import { green } from 'chalk'; import { pluralize, singularize } from 'inflection'; import { NAMESPACED_RESOURCE_MESSAGE } from '../constants'; -import { stripNamespaces, getNamespaceKey } from '../../../loader'; import { generateTimestamp } from '../../../database'; import { exists, readFile, writeFile } from '../../../fs'; import modelTemplate from '../../templates/model'; @@ -45,21 +44,23 @@ export async function controller(opts: Generator$opts): Promise { name }); - const namespace = getNamespaceKey(name); - - if (namespace !== 'root') { - const hasParent = await exists( - joinPath(cwd, dir, ...[...namespace.split('/'), 'application.js']) - ); - - if (!hasParent) { - await controller({ - ...opts, - cwd, - name: `${namespace}/application`, - attrs: [] - }); - } + const namespace = posix.dirname(name); + + if (namespace === '.') { + return; + } + + const hasParent = await exists( + joinPath(cwd, dir, namespace.split('/'), 'application.js') + ); + + if (!hasParent) { + await controller({ + ...opts, + cwd, + name: `${namespace}/application`, + attrs: [] + }); } } @@ -86,21 +87,23 @@ export async function serializer(opts: Generator$opts): Promise { name }); - const namespace = getNamespaceKey(name); - - if (namespace !== 'root') { - const hasParent = await exists( - joinPath(cwd, dir, ...[...namespace.split('/'), 'application.js']) - ); - - if (!hasParent) { - await serializer({ - ...opts, - cwd, - name: `${namespace}/application`, - attrs: [] - }); - } + const namespace = posix.dirname(name); + + if (namespace === '.') { + return; + } + + const hasParent = await exists( + joinPath(cwd, dir, ...[...namespace.split('/'), 'application.js']) + ); + + if (!hasParent) { + await serializer({ + ...opts, + cwd, + name: `${namespace}/application`, + attrs: [] + }); } } @@ -119,7 +122,7 @@ export function migration(opts: Generator$opts) { }); name = chain(name) - .pipe(stripNamespaces) + .pipe(posix.basename) .pipe(str => `${generateTimestamp()}-${str}`) .value(); @@ -149,7 +152,7 @@ export function modelMigration(opts: Generator$opts) { }); name = chain(name) - .pipe(stripNamespaces) + .pipe(posix.basename) .pipe(pluralize) .pipe(str => `${generateTimestamp()}-create-${str}`) .value(); @@ -178,7 +181,7 @@ export async function model(opts: Generator$opts): Promise { await modelMigration({ name, ...opts }); name = chain(name) - .pipe(stripNamespaces) + .pipe(posix.basename) .pipe(singularize) .value(); @@ -236,30 +239,31 @@ export async function resource(opts: Generator$opts) { await controller(opts); await serializer(opts); - if (getNamespaceKey(opts.name) !== 'root') { + if (posix.dirname(opts.name) === '.') { log(NAMESPACED_RESOURCE_MESSAGE); - } else { - const path = joinPath(opts.cwd, 'app', 'routes.js'); - const routes = chain(await readFile(path)) - .pipe(buf => buf.toString('utf8')) - .pipe(str => str.split('\n')) - .pipe(lines => lines.reduce((result, line, index, arr) => { - const closeIndex = arr.lastIndexOf('}'); - let str = result; - - if (line && index <= closeIndex) { - str += `${line}\n`; - } - - if (index + 1 === closeIndex) { - str += ` this.resource('${pluralize(opts.name)}');\n`; - } - - return str; - }, '')) - .value(); - - await writeFile(path, routes); - log(`${green('update')} app/routes.js`); + return; } + + const path = joinPath(opts.cwd, 'app', 'routes.js'); + const routes = chain(await readFile(path)) + .pipe(buf => buf.toString('utf8')) + .pipe(str => str.split('\n')) + .pipe(lines => lines.reduce((result, line, index, arr) => { + const closeIndex = arr.lastIndexOf('}'); + let str = result; + + if (line && index <= closeIndex) { + str += `${line}\n`; + } + + if (index + 1 === closeIndex) { + str += ` this.resource('${pluralize(opts.name)}');\n`; + } + + return str; + }, '')) + .value(); + + await writeFile(path, routes); + log(`${green('update')} app/routes.js`); } diff --git a/src/packages/compiler/index.js b/src/packages/compiler/index.js index e951b7bf..0b2176eb 100644 --- a/src/packages/compiler/index.js +++ b/src/packages/compiler/index.js @@ -1,19 +1,19 @@ // @flow -import path from 'path'; +import os from 'os'; +import path, { posix } from 'path'; import json from 'rollup-plugin-json'; import alias from 'rollup-plugin-alias'; import babel from 'rollup-plugin-babel'; import eslint from 'rollup-plugin-eslint'; -import nodeResolve from 'rollup-plugin-node-resolve'; +import resolve from 'rollup-plugin-node-resolve'; import { rollup } from 'rollup'; -import { rmrf, exists, readdir, readdirRec, isJSFile } from '../fs'; +import { rmrf, readdir, readdirRec, isJSFile } from '../fs'; import template from '../template'; -import uniq from '../../utils/uniq'; import onwarn from './utils/handle-warning'; -import normalizePath from './utils/normalize-path'; +import isExternal from './utils/is-external'; import createManifest from './utils/create-manifest'; import createBootScript from './utils/create-boot-script'; @@ -25,23 +25,10 @@ export async function compile(dir: string, env: string, { }: { useStrict: boolean } = {}): Promise { - let banner; - const local = path.join(__dirname, '..', 'src', 'index.js'); const entry = path.join(dir, 'dist', 'index.js'); - - const nodeModules = path.join(dir, 'node_modules'); - const luxNodeModules = path.join(__dirname, '..', 'node_modules'); - let external = await readdir(nodeModules).then(files => ( - files.filter(name => name !== 'lux-framework') - )); - - if (await exists(luxNodeModules)) { - external = uniq([ - ...external, - ...(await readdir(luxNodeModules)) - ]); - } + const external = isExternal(dir); + let banner; const assets = await Promise.all([ readdir(path.join(dir, 'app', 'models')), @@ -83,6 +70,21 @@ export async function compile(dir: string, env: string, { }) ]); + const aliases = { + app: posix.join('/', ...(dir.split(path.sep)), 'app'), + LUX_LOCAL: posix.join('/', ...(local.split(path.sep))) + }; + + if (os.platform() === 'win32') { + const [volume] = dir; + const prefix = `${volume}:/`; + + Object.assign(aliases, { + app: aliases.app.replace(prefix, ''), + LUX_LOCAL: aliases.LUX_LOCAL.replace(prefix, '') + }); + } + const bundle = await rollup({ entry, onwarn, @@ -90,16 +92,12 @@ export async function compile(dir: string, env: string, { plugins: [ alias({ resolve: ['.js'], - app: normalizePath(path.join(dir, 'app')), - LUX_LOCAL: normalizePath(local) + ...aliases }), - json(), - - nodeResolve({ + resolve({ preferBuiltins: true }), - eslint({ cwd: dir, parser: 'babel-eslint', @@ -112,8 +110,9 @@ export async function compile(dir: string, env: string, { path.join(__dirname, '..', 'src', '**') ] }), - - babel() + babel({ + exclude: 'node_modules/**' + }) ] }); @@ -137,3 +136,5 @@ export async function compile(dir: string, env: string, { useStrict: false }); } + +export { default as onwarn } from './utils/handle-warning'; diff --git a/src/packages/compiler/test/compiler.test.js b/src/packages/compiler/test/compiler.test.js new file mode 100644 index 00000000..fd3ecf6a --- /dev/null +++ b/src/packages/compiler/test/compiler.test.js @@ -0,0 +1,86 @@ +// @flow +import path from 'path'; + +import * as Rollup from 'rollup'; +import { spy, stub } from 'sinon'; +import { expect } from 'chai'; +import { it, describe, afterEach, beforeEach } from 'mocha'; + +import { getTestApp } from '../../../../test/utils/get-test-app'; +import { compile, onwarn } from '../index'; + +describe('module "compiler"', () => { + describe('#compile()', () => { + let rollupStub; + + beforeEach(() => { + rollupStub = stub(Rollup, 'rollup', () => ({ + write: () => Promise.resolve() + })); + }); + + afterEach(() => { + rollupStub.restore(); + }); + + ['use strict', 'use weak'].forEach(opt => { + describe(`- ${opt}`, () => { + it('creates an instance of rollup with the correct config', async () => { + const { path: dir } = await getTestApp(); + const entry = path.join(dir, 'dist', 'index.js') + + await compile(dir, 'test', { + useStrict: opt === 'use strict' + }); + + const { args: [rollupConfig] } = rollupStub.getCall(0); + + expect(rollupConfig).to.have.property('entry', entry); + expect(rollupConfig) + .to.have.property('plugins') + .and.be.an('array') + .with.lengthOf(5); + }); + }); + }); + }); + + describe('#onwarn()', () => { + let warnSpy; + const warnings = { + EMPTY_BUNDLE: { + code: 'EMPTY_BUNDLE', + message: 'Generated an empty bundle' + }, + UNUSED_EXTERNAL_IMPORT: { + code: 'UNUSED_EXTERNAL_IMPORT', + message: ( + `'unused', 'notused' and 'neverused' are imported from external` + + `module 'external' but never used` + ) + } + }; + + beforeEach(() => { + warnSpy = spy(console, 'warn'); + }); + + afterEach(() => { + warnSpy.restore(); + }); + + it('outputs valid warning types to stderr', () => { + onwarn(warnings.EMPTY_BUNDLE); + expect( + warnSpy.calledWithExactly(warnings.EMPTY_BUNDLE.message) + ).to.be.true; + }); + + it('ignores invalid warning types', () => { + onwarn(warnings.UNUSED_EXTERNAL_IMPORT); + expect( + warnSpy.neverCalledWith(warnings.UNUSED_EXTERNAL_IMPORT.message) + ).to.be.true; + }); + }); +}); diff --git a/src/packages/compiler/test/format-name.test.js b/src/packages/compiler/test/format-name.test.js new file mode 100644 index 00000000..19e4ae00 --- /dev/null +++ b/src/packages/compiler/test/format-name.test.js @@ -0,0 +1,49 @@ +// @flow +import path from 'path'; + +import { expect } from 'chai'; +import { it, describe, beforeEach } from 'mocha'; + +import { getTestApp } from '../../../../test/utils/get-test-app'; +import formatName from '../utils/format-name'; + +describe('module "compiler"', () => { + describe('util formatName()', () => { + let keys: Array; + + beforeEach(async () => { + const { controllers } = await getTestApp(); + + keys = Array.from(controllers.keys()); + }); + + it('transforms an array of keys into identifiers', () => { + expect(keys.map(formatName).sort()).to.deep.equal([ + 'Actions', + 'Admin$Actions', + 'Admin$Application', + 'Admin$Categorizations', + 'Admin$Comments', + 'Admin$Friendships', + 'Admin$Images', + 'Admin$Notifications', + 'Admin$Posts', + 'Admin$Reactions', + 'Admin$Tags', + 'Admin$Users', + 'Application', + 'Categorizations', + 'Comments', + 'Custom', + 'Friendships', + 'Health', + 'Images', + 'Notifications', + 'Posts', + 'Reactions', + 'Tags', + 'Users' + ]); + }); + }); +}); diff --git a/src/packages/compiler/test/is-external.test.js b/src/packages/compiler/test/is-external.test.js new file mode 100644 index 00000000..c17bda3e --- /dev/null +++ b/src/packages/compiler/test/is-external.test.js @@ -0,0 +1,58 @@ +// @flow +import path from 'path'; + +import { spy } from 'sinon'; +import { expect } from 'chai'; +import { it, describe, beforeEach } from 'mocha'; + +import isExternal from '../utils/is-external'; + +const SRC = path.join(__dirname, '..', '..', '..'); + +describe('module "compiler"', () => { + describe('util isExternal()', () => { + it('returns a function that accepts a single argument', () => { + expect(isExternal(SRC)).to.be.a('function').with.lengthOf(1); + }); + + describe('external()', () => { + let external: (id: string) => boolean; + + beforeEach(() => { + external = isExternal(SRC); + }); + + it('returns `true` for external modules', () => { + expect(external('knex')).to.be.true; + }); + + it('returns `false` for aliased file paths', () => { + expect(external('app/models/user')).to.be.false; + }); + + it('returns `false` for absolute file paths', () => { + expect(external('/absolute/path/to/app/models/user')).to.be.false; + expect(external('C:/absolute/path/to/app/models/user')).to.be.false; + expect(external( + 'C:\\absolute\\path\\to\\app\\models\\user' + )).to.be.false; + }); + + it('returns `false` for relative file paths', () => { + expect(external('./app/models/user')).to.be.false; + }); + + it('returns `false` for "LUX_LOCAL"', () => { + expect(external('LUX_LOCAL')).to.be.false; + }); + + it('returns `false` for "lux-framework"', () => { + expect(external('lux-framework')).to.be.false; + }); + + it('returns `false` for "babelHelpers"', () => { + expect(external('babelHelpers')).to.be.false; + }); + }); + }); +}); diff --git a/src/packages/compiler/utils/create-manifest.js b/src/packages/compiler/utils/create-manifest.js index b61939bb..439a49a4 100644 --- a/src/packages/compiler/utils/create-manifest.js +++ b/src/packages/compiler/utils/create-manifest.js @@ -1,5 +1,5 @@ // @flow -import { join as joinPath } from 'path'; +import { sep, posix, basename, join as joinPath } from 'path'; import { camelize, capitalize, pluralize } from 'inflection'; @@ -9,9 +9,7 @@ import tryCatch from '../../../utils/try-catch'; import underscore from '../../../utils/underscore'; import { compose } from '../../../utils/compose'; -import stripExt from './strip-ext'; import formatName from './format-name'; -import normalizePath from './normalize-path'; /** * @private @@ -21,7 +19,7 @@ function createExportStatement( path: string, isDefault: boolean = true ): string { - const normalized = normalizePath(path); + const normalized = posix.join(...path.split(sep)); if (isDefault) { return `export {\n default as ${name}\n} from '../${normalized}';\n\n`; @@ -68,7 +66,7 @@ function createWriter(file: string) { migrations: writerFor('migration', async (item) => { const path = joinPath('db', 'migrate', item); const name = chain(item) - .pipe(stripExt) + .pipe(str => basename(str, '.js')) .pipe(underscore) .pipe(str => str.substr(17)) .pipe(str => camelize(str, true)) diff --git a/src/packages/compiler/utils/format-name.js b/src/packages/compiler/utils/format-name.js index 04793a8f..c48ddd80 100644 --- a/src/packages/compiler/utils/format-name.js +++ b/src/packages/compiler/utils/format-name.js @@ -1,30 +1,24 @@ // @flow +import { posix, dirname, basename } from 'path'; + import { camelize } from 'inflection'; -import chain from '../../../utils/chain'; import underscore from '../../../utils/underscore'; - -import stripExt from './strip-ext'; -import normalizePath from './normalize-path'; +import { compose } from '../../../utils/compose'; const DOUBLE_COLON = /::/g; /** * @private */ -function applyNamespace(source: string) { - return source.replace(DOUBLE_COLON, '$'); -} +const formatName: (source: string) => string = compose( + (name: string) => name.replace(DOUBLE_COLON, '$'), + camelize, + underscore, + (name: string) => posix.join( + dirname(name), + basename(name, '.js') + ) +); -/** - * @private - */ -export default function formatName(source: string) { - return chain(source) - .pipe(normalizePath) - .pipe(stripExt) - .pipe(underscore) - .pipe(camelize) - .pipe(applyNamespace) - .value(); -} +export default formatName; diff --git a/src/packages/compiler/utils/handle-warning.js b/src/packages/compiler/utils/handle-warning.js index d582fea9..46409777 100644 --- a/src/packages/compiler/utils/handle-warning.js +++ b/src/packages/compiler/utils/handle-warning.js @@ -3,8 +3,18 @@ /** * @private */ -export default function handleWarnings(...warnings: Array): void { - warnings - .filter(warning => warning.indexOf('external dependency') < 0) - .forEach(warning => process.stderr.write(`${warning}\n`)); +type CompilerWarning = { + code: string; + message: string; +}; + +/** + * @private + */ +export default function handleWarning(warning: CompilerWarning): void { + if (warning.code === 'UNUSED_EXTERNAL_IMPORT') { + return; + } + // eslint-disable-next-line no-console + console.warn(warning.message); } diff --git a/src/packages/compiler/utils/is-external.js b/src/packages/compiler/utils/is-external.js new file mode 100644 index 00000000..6af6b043 --- /dev/null +++ b/src/packages/compiler/utils/is-external.js @@ -0,0 +1,20 @@ +// @flow +import path from 'path'; + +/** + * @private + */ +export default function isExternal(dir: string): (id: string) => boolean { + return (id: string): boolean => !( + id.startsWith('.') + || id.endsWith('lux-framework') + || id.startsWith('/') // Absolute path on Unix + || /^[A-Z]:[\\/]/.test(id) // Absolute path on Windows + || id.startsWith('app') + || id.startsWith(path.join(dir, 'app')) + || id.startsWith(path.join(dir, 'dist')) + || id === 'LUX_LOCAL' + || id === 'babelHelpers' + || id === '\u0000babelHelpers' + ); +} diff --git a/src/packages/compiler/utils/normalize-path.js b/src/packages/compiler/utils/normalize-path.js deleted file mode 100644 index a4a0621c..00000000 --- a/src/packages/compiler/utils/normalize-path.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import { BACKSLASH } from '../../../constants'; - -/** - * @private - */ -export default function normalizePath(source: string): string { - return source.replace(BACKSLASH, '/'); -} diff --git a/src/packages/compiler/utils/strip-ext.js b/src/packages/compiler/utils/strip-ext.js deleted file mode 100644 index beba362f..00000000 --- a/src/packages/compiler/utils/strip-ext.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -const EXT_PATTERN = /\.js$/; - -/** - * @private - */ -export default function stripExt(source: string): string { - return source.replace(EXT_PATTERN, ''); -} diff --git a/src/packages/fs/index.js b/src/packages/fs/index.js index 34d4825d..030f8cf9 100644 --- a/src/packages/fs/index.js +++ b/src/packages/fs/index.js @@ -1,11 +1,10 @@ // @flow import fs from 'fs'; -import { join as joinPath, resolve as resolvePath } from 'path'; +import { basename, join as joinPath, resolve as resolvePath } from 'path'; import type { Stats } from 'fs'; // eslint-disable-line no-duplicate-imports import Watcher from './watcher'; import createResolver from './utils/create-resolver'; -import createPathRemover from './utils/create-path-remover'; import type { fs$readOpts, fs$writeOpts } from './interfaces'; export { default as rmrf } from './utils/rmrf'; @@ -89,8 +88,6 @@ export function readdirRec( path: string, opts?: fs$readOpts ): Promise> { - const stripPath = createPathRemover(path); - return readdir(path, opts) .then(files => Promise.all( files.map(file => { @@ -106,13 +103,13 @@ export function readdirRec( ])) )) .then(files => files.reduce((arr, [file, children]) => { - const basename = stripPath(file); + const name = basename(file); - return [ - ...arr, - basename, - ...children.map(child => joinPath(basename, stripPath(child))) - ]; + // eslint-disable-next-line no-param-reassign + arr[arr.length] = name; + return arr.concat( + children.map(child => joinPath(name, basename(child))) + ); }, [])); } diff --git a/src/packages/fs/utils/create-path-remover.js b/src/packages/fs/utils/create-path-remover.js deleted file mode 100644 index 34401956..00000000 --- a/src/packages/fs/utils/create-path-remover.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -import { BACKSLASH, PLATFORM } from '../../../constants'; -import type { fs$PathRemover } from '../interfaces'; - -/** - * @private - */ -export default function createPathRemover(path: string): fs$PathRemover { - let pattern = new RegExp(`${path}(/)?(.+)`); - - if (PLATFORM.startsWith('win')) { - const sep = '\\\\'; - - pattern = new RegExp(`${path.replace(BACKSLASH, sep)}(${sep})?(.+)`); - } - - return source => source.replace(pattern, '$2'); -} diff --git a/src/packages/fs/utils/is-js-file.js b/src/packages/fs/utils/is-js-file.js index 8612def0..99565f06 100644 --- a/src/packages/fs/utils/is-js-file.js +++ b/src/packages/fs/utils/is-js-file.js @@ -1,9 +1,9 @@ // @flow -const REGEXP = /^(?!\.).+\.js$/; +import { extname } from 'path'; /** * @private */ export default function isJSFile(target: string): boolean { - return REGEXP.test(target); + return extname(target) === '.js'; } diff --git a/src/packages/loader/builder/utils/create-parent-builder.js b/src/packages/loader/builder/utils/create-parent-builder.js index e11c095e..6b9bf46f 100644 --- a/src/packages/loader/builder/utils/create-parent-builder.js +++ b/src/packages/loader/builder/utils/create-parent-builder.js @@ -1,5 +1,6 @@ // @flow -import { getParentKey } from '../../resolver'; +import { posix } from 'path'; + import type { Builder$Construct, Builder$ParentBuilder } from '../interfaces'; import sortByNamespace from './sort-by-namespace'; @@ -18,7 +19,7 @@ export default function createParentBuilder( if (key !== 'root') { grandparent = result.find(namespace => ( - namespace.key === getParentKey(key) + namespace.key === posix.dirname(key) )); if (grandparent) { diff --git a/src/packages/loader/index.js b/src/packages/loader/index.js index 9d0ddaba..c05dfcd0 100644 --- a/src/packages/loader/index.js +++ b/src/packages/loader/index.js @@ -18,12 +18,7 @@ export function createLoader(path: string): Loader { } export { build } from './builder'; -export { - stripNamespaces, - closestAncestor, - closestChild, - getParentKey as getNamespaceKey -} from './resolver'; +export { closestAncestor, closestChild } from './resolver'; export type { Loader, diff --git a/src/packages/loader/resolver/index.js b/src/packages/loader/resolver/index.js index b711aff5..f50fff43 100644 --- a/src/packages/loader/resolver/index.js +++ b/src/packages/loader/resolver/index.js @@ -42,7 +42,5 @@ export function resolve( }, new FreezeableMap()); } -export { default as getParentKey } from './utils/get-parent-key'; -export { default as stripNamespaces } from './utils/strip-namespaces'; export { default as closestAncestor } from './utils/closest-ancestor'; export { default as closestChild } from './utils/closest-child'; diff --git a/src/packages/loader/resolver/utils/closest-ancestor.js b/src/packages/loader/resolver/utils/closest-ancestor.js index d31d7fe3..d391d085 100644 --- a/src/packages/loader/resolver/utils/closest-ancestor.js +++ b/src/packages/loader/resolver/utils/closest-ancestor.js @@ -1,20 +1,29 @@ // @flow +import { posix } from 'path'; + import type { Bundle$Namespace } from '../../index'; export default function closestAncestor( source: Bundle$Namespace, key: string ): void | T { - const parts = key.split('/'); + const name = posix.basename(key); + let namespace = posix.dirname(key); + + if (namespace === '.') { + return source.get(name); + } + + namespace = posix.dirname(namespace); - if (parts.length > 2) { - const name = parts.pop(); - const part = `${parts.slice(0, parts.length - 1).join('/')}/${name}`; + const ancestor = source.get(posix.join(namespace, name)); - return source.get(part) || closestAncestor(source, part); - } else if (parts.length === 2) { - return source.get(parts.pop()); + if (ancestor) { + return ancestor; } - return undefined; + return closestAncestor( + source, + posix.join(posix.dirname(namespace), name) + ); } diff --git a/src/packages/loader/resolver/utils/closest-child.js b/src/packages/loader/resolver/utils/closest-child.js index 390bb75b..6eb43555 100644 --- a/src/packages/loader/resolver/utils/closest-child.js +++ b/src/packages/loader/resolver/utils/closest-child.js @@ -1,4 +1,6 @@ // @flow +import { posix } from 'path'; + import type { Bundle$Namespace } from '../../index'; export default function closestChild( @@ -7,7 +9,7 @@ export default function closestChild( ): void | T { const [[, result] = []] = Array .from(source) - .map(([path, value]) => [path.split('/').pop(), value]) + .map(([path, value]) => [posix.basename(path), value]) .filter(([resource]) => key === resource); return result; diff --git a/src/packages/loader/resolver/utils/get-parent-key.js b/src/packages/loader/resolver/utils/get-parent-key.js deleted file mode 100644 index ad1b768f..00000000 --- a/src/packages/loader/resolver/utils/get-parent-key.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow - -/** - * @private - */ -export default function getParentKey(source: string): string { - const parts = source.split('/'); - const parent = parts.slice(0, Math.max(parts.length - 1, 0)).join('/'); - - return parent || 'root'; -} diff --git a/src/packages/loader/resolver/utils/strip-namespaces.js b/src/packages/loader/resolver/utils/strip-namespaces.js deleted file mode 100644 index b6d7d1f8..00000000 --- a/src/packages/loader/resolver/utils/strip-namespaces.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import getParentKey from './get-parent-key'; - -/** - * @private - */ -export default function stripNamespaces(source: string): string { - return source.replace(`${getParentKey(source)}/`, ''); -}