feat: implement basic public api

This commit is contained in:
Igor Randjelovic
2020-11-18 14:10:44 +01:00
committed by Nathan Walker
parent ae12ee9324
commit cb7108d33c
18 changed files with 201 additions and 138 deletions

View File

@ -17,7 +17,7 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },
@ -159,7 +159,7 @@ exports[`react configuration > android > base config 1`] = `
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },
@ -294,7 +294,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },
@ -439,7 +439,7 @@ exports[`react configuration > ios > base config 1`] = `
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },

View File

@ -17,7 +17,7 @@ exports[`vue configuration for android 1`] = `
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },
@ -155,7 +155,7 @@ exports[`vue configuration for ios 1`] = `
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: [
'@nativescript/webpack/loaders', '@nativescript/webpack/dist/loaders',
'node_modules' 'node_modules'
] ]
}, },

View File

@ -1,10 +1,6 @@
import { __react } from '@nativescript/webpack'; // @ts-ignore
import Config from 'webpack-chain';
// todo: maybe mock baseConfig as we test it separately? import react from '../../src/configuration/react';
// import Config from 'webpack-chain'
// jest.mock('../../src/configuration/base', () => () => {
// return new Config()
// })
describe('react configuration', () => { describe('react configuration', () => {
const platforms = ['ios', 'android']; const platforms = ['ios', 'android'];
@ -13,7 +9,7 @@ describe('react configuration', () => {
describe(`> ${platform} >`, () => { describe(`> ${platform} >`, () => {
it(`base config`, () => { it(`base config`, () => {
expect( expect(
__react({ react(new Config(), {
[platform]: true, [platform]: true,
}).toString() }).toString()
).toMatchSnapshot(); ).toMatchSnapshot();
@ -21,7 +17,7 @@ describe('react configuration', () => {
it(`adds ReactRefreshWebpackPlugin when HMR enabled`, () => { it(`adds ReactRefreshWebpackPlugin when HMR enabled`, () => {
expect( expect(
__react({ react(new Config(), {
[platform]: true, [platform]: true,
hmr: true, hmr: true,
}).toString() }).toString()

View File

@ -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? describe.only('vue configuration', () => {
// import Config from 'webpack-chain'
// jest.mock('../../src/configuration/base', () => () => {
// return new Config()
// })
describe('vue configuration', () => {
const platforms = ['ios', 'android']; const platforms = ['ios', 'android'];
for (let platform of platforms) { for (let platform of platforms) {
it(`for ${platform}`, () => { it(`for ${platform}`, () => {
expect( expect(
__vue({ vue(new Config(), {
[platform]: true, [platform]: true,
}).toString() }).toString()
).toMatchSnapshot(); ).toMatchSnapshot();

View File

@ -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', () => { describe('@nativescript/webpack', () => {
it('exports base configs', () => { it('exports the public api', () => {
expect(webpack.angularConfig).toBeInstanceOf(Function); expect(webpack.init).toBeInstanceOf(Function);
expect(webpack.baseConfig).toBeInstanceOf(Function); expect(webpack.useConfig).toBeInstanceOf(Function);
expect(webpack.javascriptConfig).toBeInstanceOf(Function); expect(webpack.chainWebpack).toBeInstanceOf(Function);
expect(webpack.reactConfig).toBeInstanceOf(Function); expect(webpack.mergeWebpack).toBeInstanceOf(Function);
expect(webpack.svelteConfig).toBeInstanceOf(Function); expect(webpack.resolveChainableConfig).toBeInstanceOf(Function);
expect(webpack.typescriptConfig).toBeInstanceOf(Function); expect(webpack.resolveConfig).toBeInstanceOf(Function);
expect(webpack.vueConfig).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);
}); });
}); });

View File

@ -2,11 +2,15 @@
"name": "@nativescript/webpack", "name": "@nativescript/webpack",
"version": "4.0.0-dev", "version": "4.0.0-dev",
"private": true, "private": true,
"main": "index.js", "main": "dist/index.js",
"files": [
"dist"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"test": "jest" "test": "jest",
"prepack": "npm run build && cp -R src/stubs dist/stubs"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.15", "@types/jest": "^26.0.15",

View File

@ -2,9 +2,8 @@ import base from './base';
import { IWebpackEnv } from '@nativescript/webpack'; import { IWebpackEnv } from '@nativescript/webpack';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
// todo: add base configuration for angular export default function (config: Config, env: IWebpackEnv): Config {
export default function (env: IWebpackEnv): Config { base(config, env);
const config = base(env);
return config; return config;
} }

View File

@ -1,13 +1,11 @@
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { IWebpackEnv, WebpackPlatform } from './index'; import { IWebpackEnv, Platform } from '../index';
import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import { getDistPath } from '../helpers/projectHelpers'; import { getDistPath } from '../helpers/projectHelpers';
import { DefinePlugin } from 'webpack'; import { DefinePlugin } from 'webpack';
import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin'; import { WatchStateLoggerPlugin } from '../plugins/WatchStateLoggerPlugin';
// todo: add base configuration that's shared across all flavors export default function (config: Config, env: IWebpackEnv): Config {
export default function (env: IWebpackEnv): Config {
const config = new Config();
const distPath = getDistPath(env); const distPath = getDistPath(env);
const platform = determinePlatformFromEnv(env); const platform = determinePlatformFromEnv(env);
const mode = env.production ? 'production' : 'development'; const mode = env.production ? 'production' : 'development';
@ -21,7 +19,7 @@ export default function (env: IWebpackEnv): Config {
// look for loaders in // look for loaders in
// - @nativescript/webpack/loaders // - @nativescript/webpack/loaders
// - node_modules // - 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 // inspector_modules
config.when(shouldIncludeInspectorModules(env), (config) => { config.when(shouldIncludeInspectorModules(env), (config) => {
@ -95,8 +93,8 @@ export default function (env: IWebpackEnv): Config {
config.plugin('DefinePlugin').use(DefinePlugin, [ config.plugin('DefinePlugin').use(DefinePlugin, [
{ {
'global.NS_WEBPACK': true, 'global.NS_WEBPACK': true,
'global.isAndroid': platform === WebpackPlatform.android, 'global.isAndroid': platform === 'android',
'global.isIOS': platform === WebpackPlatform.ios, 'global.isIOS': platform === 'ios',
process: 'global.process', process: 'global.process',
}, },
]); ]);
@ -120,16 +118,16 @@ function shouldIncludeInspectorModules(env: IWebpackEnv): boolean {
const platform = determinePlatformFromEnv(env); const platform = determinePlatformFromEnv(env);
// todo: check if core modules are external // todo: check if core modules are external
// todo: check if we are testing // 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) { if (env?.android) {
return WebpackPlatform.android; return 'android';
} }
if (env?.ios) { if (env?.ios) {
return WebpackPlatform.ios; return 'ios';
} }
throw new Error('You need to provide a target platform!'); throw new Error('You need to provide a target platform!');

View File

@ -7,36 +7,12 @@ import svelte from './svelte';
import typescript from './typescript'; import typescript from './typescript';
import vue from './vue'; import vue from './vue';
// export chain configs export const configs = {
// todo: rename if needed base,
export { base as __base, angular as __angular, javascript as __javascript, react as __react, svelte as __svelte, typescript as __typescript, vue as __vue }; angular,
javascript,
// export final configs react,
export const baseConfig = (env: IWebpackEnv) => base(env).toConfig(); svelte,
typescript,
export const angularConfig = (env: IWebpackEnv) => angular(env).toConfig(); vue,
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',
}

View File

@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
// todo: add base configuration for core with javascript // todo: add base configuration for core with javascript
export default function (env: IWebpackEnv): Config { export default function (config: Config, env: IWebpackEnv): Config {
const config = base(env); base(config, env);
// set up xml // set up xml
config.module config.module

View File

@ -3,9 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { merge } from 'webpack-merge'; import { merge } from 'webpack-merge';
// todo: add base configuration for react export default function (config: Config, env: IWebpackEnv): Config {
export default function (env: IWebpackEnv): Config { base(config, env);
const config = base(env);
// todo: use env // todo: use env
let isAnySourceMapEnabled = true; let isAnySourceMapEnabled = true;

View File

@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
// todo: add base configuration for svelte // todo: add base configuration for svelte
export default function (env: IWebpackEnv): Config { export default function (config: Config, env: IWebpackEnv): Config {
const config = base(env); base(config, env);
return config; return config;
} }

View File

@ -3,8 +3,8 @@ import { IWebpackEnv } from '@nativescript/webpack';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
// todo: add base configuration for core // todo: add base configuration for core
export default function (env: IWebpackEnv): Config { export default function (config: Config, env: IWebpackEnv): Config {
const config = base(env); base(config, env);
return config; return config;
} }

View File

@ -1,11 +1,11 @@
import base from './base'; import base from './base';
import Config from 'webpack-chain'; import Config from 'webpack-chain';
import { VueLoaderPlugin } from 'vue-loader'; import { VueLoaderPlugin } from 'vue-loader';
import { IWebpackEnv } from './index'; import { IWebpackEnv } from '../index';
import { merge } from 'webpack-merge'; import { merge } from 'webpack-merge';
// todo: add base configuration for vue
export default function (env: IWebpackEnv): Config { export default function (config: Config, env: IWebpackEnv): Config {
const config = base(env); base(config, env);
// resolve .vue files // resolve .vue files
config.resolve.extensions.prepend('.vue'); config.resolve.extensions.prepend('.vue');

View File

@ -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;
}

View File

@ -1,55 +1,66 @@
export * from './configuration'; import Config from 'webpack-chain';
import { existsSync } from "fs"; import webpack, { config } from 'webpack';
import { getPackageJson } from './helpers/projectHelpers'; import { configs } from './configuration';
import { resolve } from "path";
export type Platform = 'android' | 'ios' | string;
export type Platform = 'android' | 'ios'; export interface IWebpackEnv {
/** [name: string]: any;
* 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)) { appPath?: string;
throw new Error(`The specified path to app directory ${appDirectory} does not exist. Unable to find entry module.`); appResourcesPath?: string;
}
android?: boolean;
ios?: boolean;
production?: boolean;
report?: boolean;
hmr?: boolean;
// todo: add others
} }
function getPackageJsonEntry(appDirectory) { let webpackChains: any[] = [];
const packageJsonSource = getPackageJson(appDirectory); let webpackMerges: any[] = [];
const entry = packageJsonSource.main; let env: IWebpackEnv = {};
if (!entry) { ////// PUBLIC API
throw new Error(`${appDirectory}/package.json must contain a 'main' attribute!`); 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') { export function chainWebpack(chainFn: (config: Config, env: IWebpackEnv) => any) {
verifyEntryModuleDirectory(appDirectory); webpackChains.push(chainFn);
}
const entry = getPackageJsonEntry(appDirectory); export function mergeWebpack(mergeFn: (config: Partial<webpack.Configuration>, env: IWebpackEnv) => any | Partial<webpack.Configuration>) {
webpackMerges.push(mergeFn);
}
const tsEntryPath = resolve(appDirectory, `${entry}.ts`); export function resolveChainableConfig() {
const jsEntryPath = resolve(appDirectory, `${entry}.js`); const config = new Config();
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) { // this applies all chain configs
throw new Error(`The entry module ${entry} specified in ` + webpackChains.forEach((chainFn) => {
`${appDirectory}/package.json doesn't exist!`) return chainFn(config, env);
} });
return entry; 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();
}

View File

@ -0,0 +1,11 @@
const webpack = require("@nativescript/webpack");
module.exports = (env) => {
webpack.init(env);
// todo: comments for common usage
return webpack.resolveConfig();
};

View File

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"rootDir": ".", "rootDir": "./src",
"baseUrl": ".", "baseUrl": ".",
"target": "es2017", "target": "es2017",
"module": "commonjs", "module": "commonjs",
@ -16,7 +16,8 @@
"paths": { "paths": {
"@nativescript/webpack": ["src"] "@nativescript/webpack": ["src"]
}, },
"esModuleInterop": true "esModuleInterop": true,
"allowSyntheticDefaultImports": true
}, },
"include": ["src"], "include": ["src"],
"exclude": ["node_modules"] "exclude": ["node_modules"]