diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap new file mode 100644 index 000000000..9498926b0 --- /dev/null +++ b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap @@ -0,0 +1,510 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`base configuration for android 1`] = ` +"{ + mode: 'development', + externals: [ + 'package.json', + '~/package.json' + ], + devtool: 'inline-source-map', + target: 'node', + output: { + path: '__jest__/platforms/android/app/src/main/assets/app', + pathinfo: false, + publicPath: '', + libraryTarget: 'commonjs', + globalObject: 'global' + }, + resolve: { + symlinks: true, + alias: { + '~': '__jest__/src', + '@': '__jest__/src' + }, + extensions: [ + '.android.ts', + '.ts', + '.android.js', + '.js', + '.android.css', + '.css', + '.android.scss', + '.scss', + '.android.json', + '.json' + ] + }, + resolveLoader: { + modules: [ + 'node_modules/@nativescript/webpack/dist/loaders', + 'node_modules' + ] + }, + module: { + rules: [ + /* config.module.rule('ts') */ + { + test: [ + /\\\\.ts$/ + ], + use: [ + /* config.module.rule('ts').use('ts-loader') */ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: true, + declaration: false + }, + getCustomTransformers: function () { /* omitted long function */ } + } + } + ] + }, + /* config.module.rule('js') */ + { + test: /\\\\.js$/, + exclude: [ + /node_modules/ + ], + use: [ + /* config.module.rule('js').use('babel-loader') */ + { + loader: 'babel-loader', + options: { + generatorOpts: { + compact: false + } + } + } + ] + }, + /* config.module.rule('css') */ + { + test: /\\\\.css$/, + use: [ + /* config.module.rule('css').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, + /* config.module.rule('css').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('css').use('postcss-loader') */ + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + 'postcss-import' + ] + } + } + } + ] + }, + /* config.module.rule('scss') */ + { + test: /\\\\.scss$/, + use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, + /* config.module.rule('scss').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('scss').use('postcss-loader') */ + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + 'postcss-import' + ] + } + } + }, + /* config.module.rule('scss').use('sass-loader') */ + { + loader: 'sass-loader' + } + ] + } + ] + }, + optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, + minimizer: [ + /* config.optimization.minimizer('TerserPlugin') */ + new TerserPlugin( + { + terserOptions: { + compress: { + collapse_vars: false, + sequences: false + }, + keep_fnames: true + } + } + ) + ] + }, + plugins: [ + /* config.plugin('ForkTsCheckerWebpackPlugin') */ + new ForkTsCheckerWebpackPlugin( + { + typescript: { + memoryLimit: 4096 + } + } + ), + /* config.plugin('CleanWebpackPlugin') */ + new CleanWebpackPlugin( + { + cleanOnceBeforeBuildPatterns: [ + '__jest__/platforms/android/app/src/main/assets/app/**/*' + ], + verbose: false + } + ), + /* config.plugin('PlatformSuffixPlugin') */ + new PlatformSuffixPlugin( + { + platform: 'android' + } + ), + /* config.plugin('FilterWarningsPlugin') */ + new FilterWarningsPlugin( + { + exclude: /System.import\\\\(\\\\) is deprecated/ + } + ), + /* config.plugin('DefinePlugin') */ + new DefinePlugin( + { + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: true, + __IOS__: false, + 'global.isAndroid': true, + 'global.isIOS': false, + process: 'global.process' + } + ), + /* config.plugin('CopyWebpackPlugin') */ + new CopyPlugin( + { + patterns: [ + { + from: 'assets/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + }, + { + from: 'fonts/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + }, + { + from: '**/*.+(jpg|png)', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + } + ] + } + ), + /* config.plugin('WatchStatePlugin') */ + new WatchStatePlugin() + ], + entry: { + bundle: [ + '@nativescript/core/globals/index.js', + '__jest__/src/app.js' + ] + } +}" +`; + +exports[`base configuration for ios 1`] = ` +"{ + mode: 'development', + externals: [ + 'package.json', + '~/package.json' + ], + devtool: 'inline-source-map', + target: 'node', + output: { + path: '__jest__/platforms/ios/jest/app', + pathinfo: false, + publicPath: '', + libraryTarget: 'commonjs', + globalObject: 'global' + }, + resolve: { + symlinks: true, + alias: { + '~': '__jest__/src', + '@': '__jest__/src' + }, + extensions: [ + '.ios.ts', + '.ts', + '.ios.js', + '.js', + '.ios.css', + '.css', + '.ios.scss', + '.scss', + '.ios.json', + '.json' + ] + }, + resolveLoader: { + modules: [ + 'node_modules/@nativescript/webpack/dist/loaders', + 'node_modules' + ] + }, + module: { + rules: [ + /* config.module.rule('ts') */ + { + test: [ + /\\\\.ts$/ + ], + use: [ + /* config.module.rule('ts').use('ts-loader') */ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: true, + declaration: false + }, + getCustomTransformers: function () { /* omitted long function */ } + } + } + ] + }, + /* config.module.rule('js') */ + { + test: /\\\\.js$/, + exclude: [ + /node_modules/ + ], + use: [ + /* config.module.rule('js').use('babel-loader') */ + { + loader: 'babel-loader', + options: { + generatorOpts: { + compact: false + } + } + } + ] + }, + /* config.module.rule('css') */ + { + test: /\\\\.css$/, + use: [ + /* config.module.rule('css').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, + /* config.module.rule('css').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('css').use('postcss-loader') */ + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + 'postcss-import' + ] + } + } + } + ] + }, + /* config.module.rule('scss') */ + { + test: /\\\\.scss$/, + use: [ + /* config.module.rule('scss').use('apply-css-loader') */ + { + loader: 'apply-css-loader' + }, + /* config.module.rule('scss').use('css2json-loader') */ + { + loader: 'css2json-loader' + }, + /* config.module.rule('scss').use('postcss-loader') */ + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + 'postcss-import' + ] + } + } + }, + /* config.module.rule('scss').use('sass-loader') */ + { + loader: 'sass-loader' + } + ] + } + ] + }, + optimization: { + splitChunks: { + cacheGroups: { + defaultVendor: { + test: /[\\\\\\\\/]node_modules[\\\\\\\\/]/, + priority: -10, + name: 'vendor', + chunks: 'all' + } + } + }, + minimizer: [ + /* config.optimization.minimizer('TerserPlugin') */ + new TerserPlugin( + { + terserOptions: { + compress: { + collapse_vars: true, + sequences: true + }, + keep_fnames: true + } + } + ) + ] + }, + plugins: [ + /* config.plugin('ForkTsCheckerWebpackPlugin') */ + new ForkTsCheckerWebpackPlugin( + { + typescript: { + memoryLimit: 4096 + } + } + ), + /* config.plugin('CleanWebpackPlugin') */ + new CleanWebpackPlugin( + { + cleanOnceBeforeBuildPatterns: [ + '__jest__/platforms/ios/jest/app/**/*' + ], + verbose: false + } + ), + /* config.plugin('PlatformSuffixPlugin') */ + new PlatformSuffixPlugin( + { + platform: 'ios' + } + ), + /* config.plugin('FilterWarningsPlugin') */ + new FilterWarningsPlugin( + { + exclude: /System.import\\\\(\\\\) is deprecated/ + } + ), + /* config.plugin('DefinePlugin') */ + new DefinePlugin( + { + __DEV__: true, + __NS_WEBPACK__: true, + __CSS_PARSER__: '\\"css-tree\\"', + __ANDROID__: false, + __IOS__: true, + 'global.isAndroid': false, + 'global.isIOS': true, + process: 'global.process' + } + ), + /* config.plugin('CopyWebpackPlugin') */ + new CopyPlugin( + { + patterns: [ + { + from: 'assets/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + }, + { + from: 'fonts/**', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + }, + { + from: '**/*.+(jpg|png)', + context: '__jest__/src', + noErrorOnMissing: true, + globOptions: { + dot: false, + ignore: [] + } + } + ] + } + ), + /* config.plugin('WatchStatePlugin') */ + new WatchStatePlugin() + ], + entry: { + bundle: [ + '@nativescript/core/globals/index.js', + '__jest__/src/app.js' + ], + 'tns_modules/@nativescript/core/inspector_modules': [ + '@nativescript/core/inspector_modules' + ] + } +}" +`; diff --git a/packages/webpack5/__tests__/configuration/base.spec.ts b/packages/webpack5/__tests__/configuration/base.spec.ts new file mode 100644 index 000000000..15e9e034b --- /dev/null +++ b/packages/webpack5/__tests__/configuration/base.spec.ts @@ -0,0 +1,46 @@ +import Config from 'webpack-chain'; +import { mockFile } from '../../scripts/jest.mockFiles'; +import base from '../../src/configuration/base'; +import { init } from '../../src'; + +describe('base configuration', () => { + const platforms = ['ios', 'android']; + + for (let platform of platforms) { + it(`for ${platform}`, () => { + init({ + [platform]: true, + }); + expect(base(new Config()).toString()).toMatchSnapshot(); + }); + } + + it('supports dotenv', () => { + mockFile('./.env', ''); + init({ + ios: true, + }); + const config = base(new Config()); + + config.plugin('DotEnvPlugin').tap((args) => { + expect(args[0].path).toEqual('__jest__/.env'); + return args; + }); + expect(config.plugin('DotEnvPlugin')).toBeDefined(); + }); + + it('supports env specific dotenv', () => { + mockFile('./.env.prod', ''); + init({ + ios: true, + env: 'prod', + }); + const config = base(new Config()); + + config.plugin('DotEnvPlugin').tap((args) => { + expect(args[0].path).toEqual('__jest__/.env.prod'); + return args; + }); + expect(config.plugin('DotEnvPlugin')).toBeDefined(); + }); +}); diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index bfbca0ea4..1c211d8cb 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -27,6 +27,7 @@ "copy-webpack-plugin": "^7.0.0", "css": "^3.0.0", "css-loader": "^5.1.1", + "dotenv-webpack": "^6.0.2", "fork-ts-checker-webpack-plugin": "^6.1.0", "loader-utils": "^2.0.0", "micromatch": "^4.0.2", diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index 02a8217c3..1e66602df 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -14,7 +14,8 @@ import { addCopyRule, applyCopyRules } from '../helpers/copyRules'; import { WatchStatePlugin } from '../plugins/WatchStatePlugin'; import { getProjectRootPath } from '../helpers/project'; import { hasDependency } from '../helpers/dependencies'; -import { IWebpackEnv } from '../index'; +import { applyDotEnvPlugin } from '../helpers/dotEnv'; +import { env as _env, IWebpackEnv } from '../index'; import { getPlatformName, getAbsoluteDistPath, @@ -22,7 +23,7 @@ import { getEntryPath, } from '../helpers/platform'; -export default function (config: Config, env: IWebpackEnv): Config { +export default function (config: Config, env: IWebpackEnv = _env): Config { const entryPath = getEntryPath(); const platform = getPlatformName(); const mode = env.production ? 'production' : 'development'; @@ -268,6 +269,9 @@ export default function (config: Config, env: IWebpackEnv): Config { }, ]); + // enable DotEnv + applyDotEnvPlugin(config); + // set up default copy rules addCopyRule('assets/**'); addCopyRule('fonts/**'); diff --git a/packages/webpack5/src/helpers/dotEnv.ts b/packages/webpack5/src/helpers/dotEnv.ts new file mode 100644 index 000000000..eb05ee405 --- /dev/null +++ b/packages/webpack5/src/helpers/dotEnv.ts @@ -0,0 +1,49 @@ +import DotEnvPlugin from 'dotenv-webpack'; +import Config from 'webpack-chain'; +import { resolve } from 'path'; + +import { getProjectRootPath } from './project'; +import { env } from '..'; +import { existsSync } from 'fs'; + +/** + * @internal + */ +export function applyDotEnvPlugin(config: Config) { + const path = getDotEnvPath(); + + config.when(path !== null, (config) => { + config.plugin('DotEnvPlugin').use(DotEnvPlugin, [ + { + path: getDotEnvPath(), + silent: true, // hide any errors + }, + ]); + }); +} + +function getDotEnvFileName(): string { + if (env.env) { + return `.env.${env.env}`; + } + + return '.env'; +} + +function getDotEnvPath(): string { + const dotEnvPath = resolve(getProjectRootPath(), '.env'); + const dotEnvWithEnvPath = resolve(getProjectRootPath(), getDotEnvFileName()); + + // look for .env. + if (existsSync(dotEnvWithEnvPath)) { + return dotEnvWithEnvPath; + } + + // fall back to .env + if (existsSync(dotEnvPath)) { + return dotEnvPath; + } + + // don't use .env + return null; +} diff --git a/packages/webpack5/src/index.ts b/packages/webpack5/src/index.ts index e6d6e5e37..be16d9dc4 100644 --- a/packages/webpack5/src/index.ts +++ b/packages/webpack5/src/index.ts @@ -12,6 +12,8 @@ import helpers from './helpers'; export interface IWebpackEnv { [name: string]: any; + env?: string; + appPath?: string; appResourcesPath?: string;