From cb7108d33c4cd881c83e915d7e3917b517a9f54d Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Wed, 18 Nov 2020 14:10:44 +0100 Subject: [PATCH] feat: implement basic public api --- .../__snapshots__/react.spec.ts.snap | 8 +- .../__snapshots__/vue.spec.ts.snap | 4 +- .../__tests__/configuration/react.spec.ts | 14 +-- .../__tests__/configuration/vue.spec.ts | 14 +-- packages/webpack5/__tests__/index.spec.ts | 37 ++++++-- packages/webpack5/package.json | 8 +- .../webpack5/src/configuration/angular.ts | 5 +- packages/webpack5/src/configuration/base.ts | 20 ++-- packages/webpack5/src/configuration/index.ts | 42 ++------- .../webpack5/src/configuration/javascript.ts | 4 +- packages/webpack5/src/configuration/react.ts | 5 +- packages/webpack5/src/configuration/svelte.ts | 4 +- .../webpack5/src/configuration/typescript.ts | 4 +- packages/webpack5/src/configuration/vue.ts | 8 +- packages/webpack5/src/helpers/temp.ts | 53 +++++++++++ packages/webpack5/src/index.ts | 93 +++++++++++-------- .../webpack5/src/stubs/default.config.stub.js | 11 +++ packages/webpack5/tsconfig.json | 5 +- 18 files changed, 201 insertions(+), 138 deletions(-) create mode 100644 packages/webpack5/src/helpers/temp.ts create mode 100644 packages/webpack5/src/stubs/default.config.stub.js diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap index b5dc376bc..68f82bc02 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap @@ -17,7 +17,7 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, @@ -159,7 +159,7 @@ exports[`react configuration > android > base config 1`] = ` }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, @@ -294,7 +294,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, @@ -439,7 +439,7 @@ exports[`react configuration > ios > base config 1`] = ` }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap index ac3ca6f5b..93d95bd7f 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap @@ -17,7 +17,7 @@ exports[`vue configuration for android 1`] = ` }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, @@ -155,7 +155,7 @@ exports[`vue configuration for ios 1`] = ` }, resolveLoader: { modules: [ - '@nativescript/webpack/loaders', + '@nativescript/webpack/dist/loaders', 'node_modules' ] }, diff --git a/packages/webpack5/__tests__/configuration/react.spec.ts b/packages/webpack5/__tests__/configuration/react.spec.ts index 366f71a62..41f2b17dd 100644 --- a/packages/webpack5/__tests__/configuration/react.spec.ts +++ b/packages/webpack5/__tests__/configuration/react.spec.ts @@ -1,10 +1,6 @@ -import { __react } from '@nativescript/webpack'; - -// todo: maybe mock baseConfig as we test it separately? -// import Config from 'webpack-chain' -// jest.mock('../../src/configuration/base', () => () => { -// return new Config() -// }) +// @ts-ignore +import Config from 'webpack-chain'; +import react from '../../src/configuration/react'; describe('react configuration', () => { const platforms = ['ios', 'android']; @@ -13,7 +9,7 @@ describe('react configuration', () => { describe(`> ${platform} >`, () => { it(`base config`, () => { expect( - __react({ + react(new Config(), { [platform]: true, }).toString() ).toMatchSnapshot(); @@ -21,7 +17,7 @@ describe('react configuration', () => { it(`adds ReactRefreshWebpackPlugin when HMR enabled`, () => { expect( - __react({ + react(new Config(), { [platform]: true, hmr: true, }).toString() diff --git a/packages/webpack5/__tests__/configuration/vue.spec.ts b/packages/webpack5/__tests__/configuration/vue.spec.ts index c1648e229..4236882c9 100644 --- a/packages/webpack5/__tests__/configuration/vue.spec.ts +++ b/packages/webpack5/__tests__/configuration/vue.spec.ts @@ -1,18 +1,14 @@ -import { __vue } from '@nativescript/webpack'; +// @ts-ignore +import Config from 'webpack-chain'; +import vue from '../../src/configuration/vue'; -// todo: maybe mock baseConfig as we test it separately? -// import Config from 'webpack-chain' -// jest.mock('../../src/configuration/base', () => () => { -// return new Config() -// }) - -describe('vue configuration', () => { +describe.only('vue configuration', () => { const platforms = ['ios', 'android']; for (let platform of platforms) { it(`for ${platform}`, () => { expect( - __vue({ + vue(new Config(), { [platform]: true, }).toString() ).toMatchSnapshot(); diff --git a/packages/webpack5/__tests__/index.spec.ts b/packages/webpack5/__tests__/index.spec.ts index 086239881..c4c08cc39 100644 --- a/packages/webpack5/__tests__/index.spec.ts +++ b/packages/webpack5/__tests__/index.spec.ts @@ -1,13 +1,32 @@ -import * as webpack from '@nativescript/webpack'; +// @ts-ignore +import Config from 'webpack-chain'; +import * as webpack from '../src'; describe('@nativescript/webpack', () => { - it('exports base configs', () => { - expect(webpack.angularConfig).toBeInstanceOf(Function); - expect(webpack.baseConfig).toBeInstanceOf(Function); - expect(webpack.javascriptConfig).toBeInstanceOf(Function); - expect(webpack.reactConfig).toBeInstanceOf(Function); - expect(webpack.svelteConfig).toBeInstanceOf(Function); - expect(webpack.typescriptConfig).toBeInstanceOf(Function); - expect(webpack.vueConfig).toBeInstanceOf(Function); + it('exports the public api', () => { + expect(webpack.init).toBeInstanceOf(Function); + expect(webpack.useConfig).toBeInstanceOf(Function); + expect(webpack.chainWebpack).toBeInstanceOf(Function); + expect(webpack.mergeWebpack).toBeInstanceOf(Function); + expect(webpack.resolveChainableConfig).toBeInstanceOf(Function); + expect(webpack.resolveConfig).toBeInstanceOf(Function); + }); + + it('applies chain configs', () => { + expect(webpack.chainWebpack).toBeInstanceOf(Function); + + const chainFn = jest.fn(); + webpack.chainWebpack(chainFn); + + // chainFn should not be called yet + expect(chainFn).not.toHaveBeenCalled(); + + // chainFn should only be called when + // resolving a chainable config + const config = webpack.resolveChainableConfig(); + + expect(chainFn).toHaveBeenCalledTimes(1); + expect(chainFn).toHaveBeenCalledWith(config, {}); + expect(config).toBeInstanceOf(Config); }); }); diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index 0288cdc57..c8413ada4 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -2,11 +2,15 @@ "name": "@nativescript/webpack", "version": "4.0.0-dev", "private": true, - "main": "index.js", + "main": "dist/index.js", + "files": [ + "dist" + ], "license": "Apache-2.0", "scripts": { "build": "tsc", - "test": "jest" + "test": "jest", + "prepack": "npm run build && cp -R src/stubs dist/stubs" }, "devDependencies": { "@types/jest": "^26.0.15", diff --git a/packages/webpack5/src/configuration/angular.ts b/packages/webpack5/src/configuration/angular.ts index 96c60d249..4f5fe5bcb 100644 --- a/packages/webpack5/src/configuration/angular.ts +++ b/packages/webpack5/src/configuration/angular.ts @@ -2,9 +2,8 @@ import base from './base'; import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; -// todo: add base configuration for angular -export default function (env: IWebpackEnv): Config { - const config = base(env); +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); return config; } diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index 4e4c88903..c25c4c31d 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -1,13 +1,11 @@ import Config from 'webpack-chain'; -import { IWebpackEnv, WebpackPlatform } from './index'; +import { IWebpackEnv, Platform } from '../index'; import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import { getDistPath } from '../helpers/projectHelpers'; import { DefinePlugin } from 'webpack'; import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin'; -// todo: add base configuration that's shared across all flavors -export default function (env: IWebpackEnv): Config { - const config = new Config(); +export default function (config: Config, env: IWebpackEnv): Config { const distPath = getDistPath(env); const platform = determinePlatformFromEnv(env); const mode = env.production ? 'production' : 'development'; @@ -21,7 +19,7 @@ export default function (env: IWebpackEnv): Config { // look for loaders in // - @nativescript/webpack/loaders // - node_modules - config.resolveLoader.modules.add('@nativescript/webpack/loaders').add('node_modules'); + config.resolveLoader.modules.add('@nativescript/webpack/dist/loaders').add('node_modules'); // inspector_modules config.when(shouldIncludeInspectorModules(env), (config) => { @@ -95,8 +93,8 @@ export default function (env: IWebpackEnv): Config { config.plugin('DefinePlugin').use(DefinePlugin, [ { 'global.NS_WEBPACK': true, - 'global.isAndroid': platform === WebpackPlatform.android, - 'global.isIOS': platform === WebpackPlatform.ios, + 'global.isAndroid': platform === 'android', + 'global.isIOS': platform === 'ios', process: 'global.process', }, ]); @@ -120,16 +118,16 @@ function shouldIncludeInspectorModules(env: IWebpackEnv): boolean { const platform = determinePlatformFromEnv(env); // todo: check if core modules are external // todo: check if we are testing - return platform === WebpackPlatform.ios; + return platform === 'ios'; } -function determinePlatformFromEnv(env: IWebpackEnv): WebpackPlatform { +function determinePlatformFromEnv(env: IWebpackEnv): Platform { if (env?.android) { - return WebpackPlatform.android; + return 'android'; } if (env?.ios) { - return WebpackPlatform.ios; + return 'ios'; } throw new Error('You need to provide a target platform!'); diff --git a/packages/webpack5/src/configuration/index.ts b/packages/webpack5/src/configuration/index.ts index f53b4be2c..e34691086 100644 --- a/packages/webpack5/src/configuration/index.ts +++ b/packages/webpack5/src/configuration/index.ts @@ -7,36 +7,12 @@ import svelte from './svelte'; import typescript from './typescript'; import vue from './vue'; -// export chain configs -// todo: rename if needed -export { base as __base, angular as __angular, javascript as __javascript, react as __react, svelte as __svelte, typescript as __typescript, vue as __vue }; - -// export final configs -export const baseConfig = (env: IWebpackEnv) => base(env).toConfig(); - -export const angularConfig = (env: IWebpackEnv) => angular(env).toConfig(); -export const javascriptConfig = (env: IWebpackEnv) => javascript(env).toConfig(); -export const reactConfig = (env: IWebpackEnv) => react(env).toConfig(); -export const svelteConfig = (env: IWebpackEnv) => svelte(env).toConfig(); -export const typescriptConfig = (env: IWebpackEnv) => typescript(env).toConfig(); -export const vueConfig = (env: IWebpackEnv) => vue(env).toConfig(); - -export interface IWebpackEnv { - [name: string]: any; - - appPath?: string; - appResourcesPath?: string; - - android?: boolean; - ios?: boolean; - - production?: boolean; - report?: boolean; - hmr?: boolean; - // todo: add others -} - -export enum WebpackPlatform { - 'ios', - 'android', -} +export const configs = { + base, + angular, + javascript, + react, + svelte, + typescript, + vue, +}; diff --git a/packages/webpack5/src/configuration/javascript.ts b/packages/webpack5/src/configuration/javascript.ts index 98c8a27e5..0993b6fe0 100644 --- a/packages/webpack5/src/configuration/javascript.ts +++ b/packages/webpack5/src/configuration/javascript.ts @@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; // todo: add base configuration for core with javascript -export default function (env: IWebpackEnv): Config { - const config = base(env); +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); // set up xml config.module diff --git a/packages/webpack5/src/configuration/react.ts b/packages/webpack5/src/configuration/react.ts index 27341e3f0..a6ea5f427 100644 --- a/packages/webpack5/src/configuration/react.ts +++ b/packages/webpack5/src/configuration/react.ts @@ -3,9 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; import { merge } from 'webpack-merge'; -// todo: add base configuration for react -export default function (env: IWebpackEnv): Config { - const config = base(env); +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); // todo: use env let isAnySourceMapEnabled = true; diff --git a/packages/webpack5/src/configuration/svelte.ts b/packages/webpack5/src/configuration/svelte.ts index 45de2976a..cbc9e8619 100644 --- a/packages/webpack5/src/configuration/svelte.ts +++ b/packages/webpack5/src/configuration/svelte.ts @@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; // todo: add base configuration for svelte -export default function (env: IWebpackEnv): Config { - const config = base(env); +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); return config; } diff --git a/packages/webpack5/src/configuration/typescript.ts b/packages/webpack5/src/configuration/typescript.ts index 4826e2233..b56f8176d 100644 --- a/packages/webpack5/src/configuration/typescript.ts +++ b/packages/webpack5/src/configuration/typescript.ts @@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack'; import Config from 'webpack-chain'; // todo: add base configuration for core -export default function (env: IWebpackEnv): Config { - const config = base(env); +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); return config; } diff --git a/packages/webpack5/src/configuration/vue.ts b/packages/webpack5/src/configuration/vue.ts index 9ee5b8dd8..ff4c474e8 100644 --- a/packages/webpack5/src/configuration/vue.ts +++ b/packages/webpack5/src/configuration/vue.ts @@ -1,11 +1,11 @@ import base from './base'; import Config from 'webpack-chain'; import { VueLoaderPlugin } from 'vue-loader'; -import { IWebpackEnv } from './index'; +import { IWebpackEnv } from '../index'; import { merge } from 'webpack-merge'; -// todo: add base configuration for vue -export default function (env: IWebpackEnv): Config { - const config = base(env); + +export default function (config: Config, env: IWebpackEnv): Config { + base(config, env); // resolve .vue files config.resolve.extensions.prepend('.vue'); diff --git a/packages/webpack5/src/helpers/temp.ts b/packages/webpack5/src/helpers/temp.ts new file mode 100644 index 000000000..c3bae972e --- /dev/null +++ b/packages/webpack5/src/helpers/temp.ts @@ -0,0 +1,53 @@ +import { existsSync } from 'fs'; +import { getPackageJson } from './projectHelpers'; +import { resolve } from 'path'; + +// todo: get rid of these or reduce them to their simplest form +// no need to do magical string replacements, loops etc... + +/** + * Function to ensure the app directory exists + * + * @param appDirectory + */ +function verifyEntryModuleDirectory(appDirectory: string) { + if (!appDirectory) { + throw new Error('Path to app directory is not specified. Unable to find entry module.'); + } + + if (!existsSync(appDirectory)) { + throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`); + } +} + +function getPackageJsonEntry(appDirectory) { + const packageJsonSource = getPackageJson(appDirectory); + const entry = packageJsonSource.main; + + if (!entry) { + throw new Error(`${appDirectory}/package.json must contain a 'main' attribute!`); + } + + return entry.replace(/\.js$/i, ''); +} + +export function getEntryModule(appDirectory: string, platform: 'android' | 'ios') { + verifyEntryModuleDirectory(appDirectory); + + const entry = getPackageJsonEntry(appDirectory); + + const tsEntryPath = resolve(appDirectory, `${entry}.ts`); + const jsEntryPath = resolve(appDirectory, `${entry}.js`); + let entryExists = existsSync(tsEntryPath) || existsSync(jsEntryPath); + if (!entryExists && platform) { + const platformTsEntryPath = resolve(appDirectory, `${entry}.${platform}.ts`); + const platformJsEntryPath = resolve(appDirectory, `${entry}.${platform}.js`); + entryExists = existsSync(platformTsEntryPath) || existsSync(platformJsEntryPath); + } + + if (!entryExists) { + throw new Error(`The entry module ${entry} specified in ` + `${appDirectory}/package.json doesn't exist!`); + } + + return entry; +} diff --git a/packages/webpack5/src/index.ts b/packages/webpack5/src/index.ts index 5a70f96a9..1caf9fb76 100644 --- a/packages/webpack5/src/index.ts +++ b/packages/webpack5/src/index.ts @@ -1,55 +1,66 @@ -export * from './configuration'; -import { existsSync } from "fs"; -import { getPackageJson } from './helpers/projectHelpers'; -import { resolve } from "path"; +import Config from 'webpack-chain'; +import webpack, { config } from 'webpack'; +import { configs } from './configuration'; +export type Platform = 'android' | 'ios' | string; -export type Platform = 'android' | 'ios'; -/** - * Function to ensure the app directory exists - * - * @param appDirectory - */ -function verifyEntryModuleDirectory(appDirectory: string) { - if (!appDirectory) { - throw new Error("Path to app directory is not specified. Unable to find entry module."); - } +export interface IWebpackEnv { + [name: string]: any; - if (!existsSync(appDirectory)) { - throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`); - } + appPath?: string; + appResourcesPath?: string; + + android?: boolean; + ios?: boolean; + + production?: boolean; + report?: boolean; + hmr?: boolean; + // todo: add others } -function getPackageJsonEntry(appDirectory) { - const packageJsonSource = getPackageJson(appDirectory); - const entry = packageJsonSource.main; +let webpackChains: any[] = []; +let webpackMerges: any[] = []; +let env: IWebpackEnv = {}; - if (!entry) { - throw new Error(`${appDirectory}/package.json must contain a 'main' attribute!`); - } +////// PUBLIC API +export const defaultConfigs = configs; - return entry.replace(/\.js$/i, ""); +export function init(_env: IWebpackEnv) { + if (_env) { + env = _env; + } + // todo: determine default config based on deps and print **useful** error if it fails. } +export function useConfig(config: 'angular' | 'javascript' | 'react' | 'svelte' | 'typescript' | 'vue') { + webpackChains.push(configs[config]); +} -export function getEntryModule (appDirectory: string, platform: 'android' | 'ios') { - verifyEntryModuleDirectory(appDirectory); +export function chainWebpack(chainFn: (config: Config, env: IWebpackEnv) => any) { + webpackChains.push(chainFn); +} - const entry = getPackageJsonEntry(appDirectory); +export function mergeWebpack(mergeFn: (config: Partial, env: IWebpackEnv) => any | Partial) { + webpackMerges.push(mergeFn); +} - const tsEntryPath = resolve(appDirectory, `${entry}.ts`); - const jsEntryPath = resolve(appDirectory, `${entry}.js`); - let entryExists = existsSync(tsEntryPath) || existsSync(jsEntryPath); - if (!entryExists && platform) { - const platformTsEntryPath = resolve(appDirectory, `${entry}.${platform}.ts`); - const platformJsEntryPath = resolve(appDirectory, `${entry}.${platform}.js`); - entryExists = existsSync(platformTsEntryPath) || existsSync(platformJsEntryPath); - } +export function resolveChainableConfig() { + const config = new Config(); - if (!entryExists) { - throw new Error(`The entry module ${entry} specified in ` + - `${appDirectory}/package.json doesn't exist!`) - } + // this applies all chain configs + webpackChains.forEach((chainFn) => { + return chainFn(config, env); + }); - return entry; -}; \ No newline at end of file + return config; +} + +export function resolveConfig(chainableConfig = resolveChainableConfig()) { + // todo: warn if no base config + + // todo: apply merges from webpackMerges + + // return a config usable by webpack + return chainableConfig.toConfig(); +} diff --git a/packages/webpack5/src/stubs/default.config.stub.js b/packages/webpack5/src/stubs/default.config.stub.js new file mode 100644 index 000000000..07275b43b --- /dev/null +++ b/packages/webpack5/src/stubs/default.config.stub.js @@ -0,0 +1,11 @@ +const webpack = require("@nativescript/webpack"); + +module.exports = (env) => { + webpack.init(env); + + // todo: comments for common usage + + return webpack.resolveConfig(); +}; + + diff --git a/packages/webpack5/tsconfig.json b/packages/webpack5/tsconfig.json index b39bb86db..ffd723f2e 100644 --- a/packages/webpack5/tsconfig.json +++ b/packages/webpack5/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "rootDir": ".", + "rootDir": "./src", "baseUrl": ".", "target": "es2017", "module": "commonjs", @@ -16,7 +16,8 @@ "paths": { "@nativescript/webpack": ["src"] }, - "esModuleInterop": true + "esModuleInterop": true, + "allowSyntheticDefaultImports": true }, "include": ["src"], "exclude": ["node_modules"]