hauler 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ // @flow
2
+
3
+ import ExtractTextPlugin from 'extract-text-webpack-plugin';
4
+ import * as webpack from 'webpack';
5
+
6
+ import devServerConfigFactory from './dev_server_config_factory';
7
+ import compilerConfigFactory from './compiler_config_factory';
8
+
9
+ function getPlugins(env: string) {
10
+ let plugins = [
11
+ new webpack.ProvidePlugin({ fetch: 'exports?self.fetch!whatwg-fetch' }),
12
+ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(env) } }),
13
+ new webpack.optimize.CommonsChunkPlugin({
14
+ name: 'vendor',
15
+ children: true,
16
+ minChunks: 2,
17
+ async: true,
18
+ }),
19
+ ];
20
+
21
+ if (env === 'development') {
22
+ plugins = plugins.concat([
23
+ new webpack.NoErrorsPlugin(),
24
+ ]);
25
+ }
26
+
27
+ if (env === 'production') {
28
+ plugins = plugins.concat([
29
+ new webpack.optimize.OccurrenceOrderPlugin(true),
30
+ new webpack.optimize.DedupePlugin(),
31
+ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),
32
+ new ExtractTextPlugin('[name].[contenthash].css'),
33
+ ]);
34
+ }
35
+
36
+ return plugins;
37
+ }
38
+
39
+ function configFactory(env: string) {
40
+ const entries = {};
41
+
42
+ // individual loaders so that they can be replaced separately
43
+ const javascriptLoader = {
44
+ test: /\.jsx?$/,
45
+ loader: 'babel',
46
+ exclude: /node_modules/,
47
+ query: {
48
+ presets: ['es2015', 'react', 'stage-2'],
49
+ plugins: ['transform-class-properties'],
50
+ },
51
+ };
52
+
53
+ const sassLoader = {
54
+ test: /\.scss$/,
55
+ loader: 'style!css!sass',
56
+ };
57
+
58
+ const fontLoader = {
59
+ test: /\.(eot|svg|ttf|woff|woff2)$/,
60
+ loader: 'file',
61
+ };
62
+
63
+ const imageLoader = {
64
+ test: /\.(jpg|png|gif)$/,
65
+ loaders: [
66
+ 'file',
67
+ 'image-webpack?{progressive:true, optimizationLevel: 7, interlaced: false, pngquant:{quality: "65-90", speed: 4}}',
68
+ ],
69
+ };
70
+
71
+ if (env === 'production') {
72
+ javascriptLoader.query.plugins = javascriptLoader.query.plugins.concat([
73
+ 'transform-react-remove-prop-types',
74
+ 'transform-react-constant-elements',
75
+ 'transform-react-inline-elements',
76
+ ]);
77
+
78
+ sassLoader.loader = ExtractTextPlugin.extract(
79
+ 'style',
80
+ sassLoader.loader.replace('style!', '')
81
+ );
82
+ }
83
+
84
+ const appendPlugins = [];
85
+ const plugins = getPlugins(env);
86
+ const prependPlugins = [];
87
+
88
+ const publicPath = '/assets';
89
+
90
+ const devServer = devServerConfigFactory(env);
91
+
92
+ const compiler = compilerConfigFactory(env);
93
+
94
+ return {
95
+ entries,
96
+ javascriptLoader,
97
+ sassLoader,
98
+ fontLoader,
99
+ imageLoader,
100
+ prependPlugins,
101
+ plugins,
102
+ appendPlugins,
103
+ publicPath,
104
+ devServer,
105
+ compiler,
106
+ };
107
+ }
108
+
109
+ export default configFactory;
data/src/index.js CHANGED
@@ -1,122 +1,43 @@
1
1
  // @flow
2
- // NOTE: This code is executed in a Gem, which makes any npm package unavailable
3
2
 
4
- const compilerDefaultsFactory = require('./defaults/compiler_config_factory');
5
- const devServerDefaultsFactory = require('./defaults/dev_server_config_factory');
6
- const utils = require('./utils');
3
+ import * as utils from './utils';
7
4
 
8
- function extractLoaders(config: ProjectConfig): Array<WebpackLoader> {
9
- return utils.compact([
10
- config.javascriptLoader,
11
- config.sassLoader,
12
- config.fontLoader,
13
- config.imageLoader,
14
- ]);
5
+ function getProjectConfig(env: string): ProjectConfig {
6
+ const projectConfigFactory = require(utils.railsPath('~config/hauler.js'));
7
+ return projectConfigFactory(env);
15
8
  }
16
9
 
17
- function extractResolve(config: ProjectConfig): WebpackResolveConfig {
18
- const resolve = config.compiler && config.compiler.resolve;
19
- const modules = resolve && resolve.modules || ['node_modules'];
20
- return utils.deepMerge(resolve, { modules: utils.railsPath(modules) });
10
+ function getProjectDefaults(env: string): ProjectConfig {
11
+ const projectDefaultsFactory = require('./defaults/project_config_factory');
12
+ return projectDefaultsFactory.default(env);
21
13
  }
22
14
 
23
- function extractPlugins(config: ProjectConfig): Array<WebpackPlugin> {
24
- const prepend = config.prependPlugins || [];
25
- const plugins = config.plugins || [];
26
- const appendPlugins = config.appendPlugins || [];
27
- return prepend.concat(plugins).concat(appendPlugins);
28
- }
29
-
30
- function extractModule(config: ProjectConfig): WebpackModuleConfig {
31
- const module = config.compiler && config.compiler.module;
32
- return utils.deepMerge(module, {
33
- loaders: extractLoaders(config),
34
- });
35
- }
36
-
37
- function extractOutput(config: ProjectConfig): WebpackOutputConfig {
38
- const output = config.compiler && config.compiler.output || {};
39
- const path = output.path || '';
40
- return utils.deepMerge(output, {
41
- path: utils.railsPath(path),
42
- });
43
- }
44
-
45
- function parseToCompilerConfig(config: ProjectConfig): WebpackConfig {
46
- return {
47
- entry: utils.railsPath(config.entries),
48
- output: extractOutput(config),
49
- module: extractModule(config),
50
- plugins: extractPlugins(config),
51
- resolve: extractResolve(config),
15
+ function getConfigFactory(): ProjectConfigFactory {
16
+ return (env: string) => {
17
+ const projectDefaults = getProjectDefaults(env);
18
+ const projectConfig = getProjectConfig(env);
19
+ const config = utils.mergeProjectConfig(projectDefaults, projectConfig);
20
+ return config;
52
21
  };
53
22
  }
54
23
 
55
- function extractPublicPath(config: WebpackDevServerConfig): string {
56
- const publicPath = config.publicPath;
57
- if (publicPath == null) {
58
- return '';
24
+ export function getConfig(env: string, railsRoot?: string) {
25
+ if (railsRoot != null) {
26
+ utils.setRailsRoot(railsRoot);
59
27
  }
60
28
 
61
- if (publicPath.indexOf('http') !== -1) {
62
- return publicPath;
63
- }
64
-
65
- const host = config.host || 'localhost';
66
- const port = config.port || 30001;
67
- return `http://${host}:${port}/${publicPath.replace(/^\//, '')}`;
68
- }
69
-
70
- function prepareDevServerConfig(config: WebpackDevServerConfig): WebpackDevServerConfig {
71
- const output = Object.assign({}, config, {
72
- publicPath: extractPublicPath(config),
73
- });
74
-
75
- return output;
29
+ const configFactory = getConfigFactory();
30
+ return configFactory(env);
76
31
  }
77
32
 
78
- const projectConfig: ProjectConfig = require(utils.railsPath('~config/hauler.js'));
79
-
80
- /**
81
- * Returns a factory for getting the project webpack dev server configuration using the
82
- * value of the `devServer` property in the result of `{Rails.root}/config/hauler.js`
83
- */
84
- function webpackDevServerConfigFactory(defaultsFactory: DevServerConfigFactory) {
85
- return (env: string): WebpackDevServerConfig => {
86
- const defaultDevServerConfig = defaultsFactory(env);
87
- const projectDevServerConfig = projectConfig.devServer;
88
- const devServerConfig = utils.deepMerge(defaultDevServerConfig, projectDevServerConfig);
89
- return prepareDevServerConfig(devServerConfig);
90
- };
33
+ export function getDevServerConfig(env: string, railsRoot?: string): WebpackDevServerConfig {
34
+ const config = getConfig(env, railsRoot);
35
+ return utils.extractDevServerConfig(config);
91
36
  }
92
37
 
93
- function webpackCompilerConfigFactory(defaultsFactory: ProjectConfigFactory) {
94
- return (env: string) => {
95
- const defaultProjectConfig = defaultsFactory(env);
96
- const haulerProjectConfig = utils.deepMerge(defaultProjectConfig, projectConfig);
97
- const webpackConfig = parseToCompilerConfig(haulerProjectConfig);
98
- return utils.deepMerge(webpackConfig, projectConfig.compiler || {});
99
- };
38
+ export function getCompilerConfig(env: string, railsRoot?: string): WebpackConfig {
39
+ const config = getConfig(env, railsRoot);
40
+ return utils.extractCompilerConfig(config);
100
41
  }
101
42
 
102
- const Hauler = {
103
- getCompilerConfigFactory() {
104
- return webpackCompilerConfigFactory(compilerDefaultsFactory);
105
- },
106
-
107
- getCompilerConfig(env: string) {
108
- const configFactory = Hauler.getCompilerConfigFactory();
109
- return configFactory(env);
110
- },
111
-
112
- getDevServerConfig(env: string) {
113
- const configFactory = Hauler.getDevServerConfigFactory();
114
- return configFactory(env);
115
- },
116
-
117
- getDevServerConfigFactory() {
118
- return webpackDevServerConfigFactory(devServerDefaultsFactory);
119
- },
120
- };
121
-
122
- module.exports = Hauler;
43
+ export { getEnvName } from './utils';
@@ -0,0 +1,132 @@
1
+ jest.disableAutomock();
2
+
3
+ const utils = require('../extract_config');
4
+ const pathUtils = require('../path');
5
+
6
+ describe('extractDevServerConfig', () => {
7
+ let projectConfigFactory;
8
+ let projectConfig;
9
+
10
+ beforeEach(() => {
11
+ projectConfigFactory = require('../../defaults/project_config_factory').default;
12
+ projectConfig = projectConfigFactory('development');
13
+ });
14
+
15
+ describe('contentBase', () => {
16
+ it('resolves to railsRoot', () => {
17
+ pathUtils.setRailsRoot('/var/www/hauler');
18
+ projectConfig.devServer.contentBase = '~lib/assets';
19
+ const config = utils.extractDevServerConfig(projectConfig);
20
+ expect(config.contentBase).toBe('/var/www/hauler/lib/assets');
21
+ });
22
+ });
23
+
24
+ describe('publicPath', () => {
25
+ it('defaults to http://localhost:3001/assets', () => {
26
+ const config = utils.extractDevServerConfig({});
27
+ expect(config.publicPath).toBe('http://localhost:3001/assets');
28
+ });
29
+
30
+ it('leaves a full URL intact', () => {
31
+ Object.assign(projectConfig.devServer, { host: 'hauler.test', port: 8080 });
32
+ projectConfig.publicPath = 'https://my.hauler.test:4005/my-assets';
33
+ const config = utils.extractDevServerConfig(projectConfig);
34
+ expect(config.publicPath).toBe('https://my.hauler.test:4005/my-assets');
35
+ });
36
+
37
+ it('updates to a full URL', () => {
38
+ Object.assign(projectConfig.devServer, { host: 'hauler.test', port: 8080 });
39
+ projectConfig.publicPath = '/my-assets';
40
+ const config = utils.extractDevServerConfig(projectConfig);
41
+ expect(config.publicPath).toBe('http://hauler.test:8080/my-assets');
42
+ });
43
+
44
+ it('sets the protocol according to the port', () => {
45
+ Object.assign(projectConfig.devServer, { port: 80 });
46
+ let config = utils.extractDevServerConfig(projectConfig);
47
+ expect(config.publicPath).toMatch('^http://localhost/');
48
+
49
+ Object.assign(projectConfig.devServer, { port: 443 });
50
+ config = utils.extractDevServerConfig(projectConfig);
51
+ expect(config.publicPath).toMatch('^https://localhost/');
52
+ });
53
+ });
54
+ });
55
+
56
+ describe('extractCompilerConfig', () => {
57
+ let projectConfigFactory;
58
+ let projectConfig;
59
+
60
+ beforeEach(() => {
61
+ pathUtils.setRailsRoot('/var/www/hauler');
62
+ projectConfigFactory = require('../../defaults/project_config_factory').default;
63
+ projectConfig = projectConfigFactory('development');
64
+ });
65
+
66
+ it('resolves #context to railsRoot', () => {
67
+ projectConfig.compiler.context = '~';
68
+ const config = utils.extractCompilerConfig(projectConfig);
69
+ expect(config.context).toBe('/var/www/hauler/');
70
+ });
71
+
72
+ it('assigns projectConfig#entries to #entry', () => {
73
+ projectConfig.entries = { main: 'main.js' };
74
+ const config = utils.extractCompilerConfig(projectConfig);
75
+ expect(config.entry).toEqual({ main: 'main.js' });
76
+ });
77
+
78
+ it('resolves #entry to railsRoot', () => {
79
+ projectConfig.entries = { main: '~lib/assets/hauler/main.js', counter: './counter' };
80
+ const config = utils.extractCompilerConfig(projectConfig);
81
+ expect(config.entry).toEqual({
82
+ main: '/var/www/hauler/lib/assets/hauler/main.js',
83
+ counter: './counter',
84
+ });
85
+ });
86
+
87
+ it('resolves #output.path to railsRoot', () => {
88
+ projectConfig.compiler.output.path = '~public/assets';
89
+ const config = utils.extractCompilerConfig(projectConfig);
90
+ expect(config.output.path).toBe('/var/www/hauler/public/assets');
91
+ });
92
+
93
+ it('resolves #output.publicPath to a full Url using the devServer', () => {
94
+ projectConfig.publicPath = '/test-assets';
95
+ Object.assign(projectConfig.devServer, { host: 'asset-host', port: 4005 });
96
+ const config = utils.extractCompilerConfig(projectConfig);
97
+ expect(config.output.publicPath).toBe('http://asset-host:4005/test-assets');
98
+ });
99
+
100
+ describe('#module.loaders', () => {
101
+ it('extracts the loaders from the project config', () => {
102
+ Object.assign(projectConfig, {
103
+ javascriptLoader: 'js',
104
+ sassLoader: 'sass',
105
+ fontLoader: 'font',
106
+ imageLoader: 'image',
107
+ });
108
+ const config = utils.extractCompilerConfig(projectConfig);
109
+ expect(config.module.loaders).toEqual(['js', 'sass', 'font', 'image']);
110
+ });
111
+
112
+ it('removes blank loaders', () => {
113
+ Object.assign(projectConfig, {
114
+ javascriptLoader: 'js',
115
+ sassLoader: null,
116
+ fontLoader: null,
117
+ imageLoader: 'image',
118
+ });
119
+ const config = utils.extractCompilerConfig(projectConfig);
120
+ expect(config.module.loaders).toEqual(['js', 'image']);
121
+ });
122
+
123
+ it('appends loaders in #compiler.module.loaders', () => {
124
+ Object.assign(projectConfig, {
125
+ javascriptLoader: 'js', sassLoader: null, fontLoader: null, imageLoader: null,
126
+ });
127
+ projectConfig.module = { loaders: ['other'] };
128
+ const config = utils.extractCompilerConfig(projectConfig);
129
+ expect(config.module.loaders).toEqual(['js', 'other']);
130
+ });
131
+ });
132
+ });
@@ -0,0 +1,75 @@
1
+ jest.unmock('../merge_config');
2
+
3
+ const defaults = {
4
+ entries: { main: 'my-main-entry.js' },
5
+ javascriptLoader: { test: 'jsLoader' },
6
+ sassLoader: { test: 'sassLoader' },
7
+ fontLoader: { test: 'fontLoader' },
8
+ imageLoader: { test: 'imageLoader' },
9
+ prependPlugins: ['prepend'],
10
+ plugins: ['plugins'],
11
+ appendPlugins: ['append'],
12
+ publicPath: '/my-assets',
13
+ devServer: {
14
+ stats: { colors: true },
15
+ },
16
+ compiler: {
17
+ output: { filename: '[name]-[hash].js' },
18
+ module: { loaders: [{ test: /\.js$/ }] },
19
+ resolve: { alias: { xyz: '/absolute/path/to/file.js' } },
20
+ },
21
+ };
22
+
23
+ describe('mergeProjectConfig', () => {
24
+ let misc;
25
+ let utils;
26
+
27
+ beforeEach(() => {
28
+ misc = require('../misc');
29
+ misc.merge.mockReturnValue({});
30
+
31
+ utils = require('../merge_config');
32
+ });
33
+
34
+ it('merges the source into the defaults', () => {
35
+ const source = { entries: { main: 'application.js' } };
36
+ utils.mergeProjectConfig(defaults, source);
37
+ expect(misc.merge).toBeCalledWith(defaults, source);
38
+ });
39
+
40
+ it('merges the source.devServer into the defaults.devServer', () => {
41
+ const devServer = { contentBase: '/', quiet: true };
42
+ utils.mergeProjectConfig(defaults, { devServer });
43
+ expect(misc.merge).toBeCalledWith(defaults.devServer, devServer);
44
+ });
45
+
46
+ it('merges the source.devServer.stats into the defaults.devServer.stats', () => {
47
+ const stats = { hash: true };
48
+ utils.mergeProjectConfig(defaults, { devServer: { stats } });
49
+ expect(misc.merge).toBeCalledWith(defaults.devServer.stats, stats);
50
+ });
51
+
52
+ it('merges the source.compiler into the defaults.compiler', () => {
53
+ const compiler = { context: '/', cache: false };
54
+ utils.mergeProjectConfig(defaults, { compiler });
55
+ expect(misc.merge).toBeCalledWith(defaults.compiler, compiler);
56
+ });
57
+
58
+ it('merges the source.compiler.output into the defaults.compiler.output', () => {
59
+ const output = { filename: '[name].js', chunkFilename: '[name].chunk.js' };
60
+ utils.mergeProjectConfig(defaults, { compiler: { output } });
61
+ expect(misc.merge).toBeCalledWith(defaults.compiler.output, output);
62
+ });
63
+
64
+ it('merges the source.compiler.module into the defaults.compiler.module', () => {
65
+ const module = { loaders: [{ test: /\.css$/ }] };
66
+ utils.mergeProjectConfig(defaults, { compiler: { module } });
67
+ expect(misc.merge).toBeCalledWith(defaults.compiler.module, module);
68
+ });
69
+
70
+ it('merges the source.compiler.resolve into the defaults.compiler.resolve', () => {
71
+ const resolve = { alias: { xyz: './dir' } };
72
+ utils.mergeProjectConfig(defaults, { compiler: { resolve } });
73
+ expect(misc.merge).toBeCalledWith(defaults.compiler.resolve, resolve);
74
+ });
75
+ });
@@ -0,0 +1,26 @@
1
+ jest.unmock('../path');
2
+
3
+ const utils = require('../path');
4
+
5
+ describe('pathJoin', () => {
6
+ it('leaves the leading slash on the first piece', () => {
7
+ expect(utils.pathJoin('/hello', 'world')).toBe('/hello/world');
8
+ expect(utils.pathJoin('hello', 'world')).toBe('hello/world');
9
+ });
10
+
11
+ it('cleans any lagging slash', () => {
12
+ expect(utils.pathJoin('hello', 'world/')).toBe('hello/world');
13
+ });
14
+
15
+ it('removes any leading or lagging spaces', () => {
16
+ expect(utils.pathJoin(' hello ', ' world ')).toBe('hello/world');
17
+ });
18
+
19
+ it('removes duplicate slashes', () => {
20
+ expect(utils.pathJoin('hello///', '///world///')).toBe('hello/world');
21
+ });
22
+
23
+ it('ignores null pieces', () => {
24
+ expect(utils.pathJoin(null, 'hello', null, 'world', null)).toBe('hello/world');
25
+ });
26
+ });
@@ -0,0 +1,103 @@
1
+ // @flow
2
+
3
+ import url from 'url';
4
+ import { railsPath } from './path';
5
+ import { compact } from './misc';
6
+
7
+ function resolveValuesRailsPath(entries: Object = {}): Object {
8
+ const keys = Object.keys(entries);
9
+ const reducer = (output, key) => Object.assign(output, { [key]: railsPath(entries[key]) });
10
+ return keys.reduce(reducer, {});
11
+ }
12
+
13
+ function extractLoaders(config: ProjectConfig): Array<WebpackLoader> {
14
+ const baseLoaders = compact([
15
+ config.javascriptLoader,
16
+ config.sassLoader,
17
+ config.fontLoader,
18
+ config.imageLoader,
19
+ ]);
20
+
21
+ const customLoaders = config.module && config.module.loaders || [];
22
+ return baseLoaders.concat(customLoaders);
23
+ }
24
+
25
+ function extractResolve(config: ProjectConfig): WebpackResolveConfig {
26
+ const resolveConfig = config.compiler && config.compiler.resolve || {};
27
+ return Object.assign(resolveConfig, {
28
+ alias: resolveValuesRailsPath(resolveConfig.alias),
29
+ root: railsPath(resolveConfig.root),
30
+ });
31
+ }
32
+
33
+ function extractPlugins(config: ProjectConfig): Array<WebpackPlugin> {
34
+ const prepend = config.prependPlugins || [];
35
+ const plugins = config.plugins || [];
36
+ const appendPlugins = config.appendPlugins || [];
37
+ return prepend.concat(plugins).concat(appendPlugins);
38
+ }
39
+
40
+ function extractModule(config: ProjectConfig): WebpackModuleConfig {
41
+ const moduleConfig = config.compiler && config.compiler.module || {};
42
+ return Object.assign(moduleConfig, {
43
+ loaders: extractLoaders(config),
44
+ });
45
+ }
46
+
47
+ function formatUrl(urlObject: Object) {
48
+ return url.format(urlObject);
49
+ }
50
+
51
+ function extractPublicPath(config: ProjectConfig): string {
52
+ const pathUrl = url.parse(config.publicPath || '/assets');
53
+ const port = String(config.devServer && config.devServer.port || '3001');
54
+
55
+ if (!pathUrl.protocol) {
56
+ pathUrl.protocol = port === '443' ? 'https' : 'http';
57
+ }
58
+
59
+ if (!pathUrl.hostname) {
60
+ pathUrl.hostname = config.devServer && config.devServer.host || 'localhost';
61
+ }
62
+
63
+ if (!pathUrl.port && port !== '80' && port !== '443') {
64
+ pathUrl.port = port;
65
+ }
66
+
67
+ return formatUrl(pathUrl);
68
+ }
69
+
70
+ function extractOutput(config: ProjectConfig): WebpackOutputConfig {
71
+ const outputConfig = config.compiler && config.compiler.output || {};
72
+ return Object.assign(outputConfig, {
73
+ path: railsPath(outputConfig.path),
74
+ publicPath: extractPublicPath(config),
75
+ });
76
+ }
77
+
78
+ function resolveContentBase(contentBase?: string): ?string {
79
+ if (!contentBase) {
80
+ return undefined;
81
+ }
82
+
83
+ return railsPath(contentBase);
84
+ }
85
+
86
+ export function extractCompilerConfig(config: ProjectConfig): WebpackConfig {
87
+ const compilerConfig = Object.assign({}, config.compiler);
88
+ return Object.assign({}, compilerConfig, {
89
+ context: railsPath(compilerConfig.context),
90
+ entry: resolveValuesRailsPath(config.entries),
91
+ output: extractOutput(config),
92
+ module: extractModule(config),
93
+ resolve: extractResolve(config),
94
+ plugins: extractPlugins(config),
95
+ });
96
+ }
97
+
98
+ export function extractDevServerConfig(config: ProjectConfig): WebpackDevServerConfig {
99
+ return Object.assign({}, config.devServer, {
100
+ contentBase: resolveContentBase(config.devServer && config.devServer.contentBase),
101
+ publicPath: extractPublicPath(config),
102
+ });
103
+ }
@@ -0,0 +1,6 @@
1
+ // @flow
2
+
3
+ export * from './extract_config';
4
+ export * from './merge_config';
5
+ export * from './misc';
6
+ export * from './path';
@@ -0,0 +1,34 @@
1
+ // @flow
2
+
3
+ import * as misc from './misc';
4
+
5
+ function mergeCompilerConfig(
6
+ target: WebpackConfig = {},
7
+ source: WebpackConfig = {}
8
+ ): WebpackConfig {
9
+ const output = misc.merge(target, source);
10
+ return Object.assign(output, {
11
+ output: misc.merge(target.output, source.output),
12
+ module: misc.merge(target.module, source.module),
13
+ resolve: misc.merge(target.resolve, source.resolve),
14
+ });
15
+ }
16
+
17
+ function mergeDevServerConfig(
18
+ target: WebpackDevServerConfig = {},
19
+ source: WebpackDevServerConfig = {}
20
+ ): WebpackDevServerConfig {
21
+ const output = misc.merge(target, source);
22
+ return Object.assign(output, {
23
+ stats: misc.merge(target.stats, source.stats),
24
+ });
25
+ }
26
+
27
+ export function mergeProjectConfig(target: ProjectConfig, source: ProjectConfig): ProjectConfig {
28
+ const output = misc.merge(target, source);
29
+ return Object.assign(output, {
30
+ entries: source.entries || target.entries || {},
31
+ devServer: mergeDevServerConfig(target.devServer, source.devServer),
32
+ compiler: mergeCompilerConfig(target.compiler, source.compiler),
33
+ });
34
+ }