Skip to content

Commit

Permalink
Add getWebpackConfig and getWebpackConfigSync methods to node API (
Browse files Browse the repository at this point in the history
  • Loading branch information
jaridmargolin authored and tannerlinsley committed Mar 28, 2019
1 parent 7e85052 commit 280d7c0
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 34 deletions.
6 changes: 6 additions & 0 deletions packages/react-static/node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const { default: bundle } = require('../lib/commands/bundle')
const { default: exporter } = require('../lib/commands/export')
const { reloadRoutes } = require('../lib/static/webpack')
const { default: makePageRoutes } = require('../lib/node/makePageRoutes')
const { default: getWebpackConfig } = require('../lib/node/getWebpackConfig')
const {
default: getWebpackConfigSync,
} = require('../lib/node/getWebpackConfigSync')
const { normalizeRoutes } = require('../lib/static/getConfig')
const { default: createSharedData } = require('../lib/static/createSharedData')

Expand All @@ -16,6 +20,8 @@ module.exports = {
export: exporter,
reloadRoutes,
makePageRoutes,
getWebpackConfig,
getWebpackConfigSync,
normalizeRoutes,
createSharedData,
}
3 changes: 3 additions & 0 deletions packages/react-static/src/browser/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ export function makeHookReducer(plugins = [], hook, { sync } = {}) {
return (value, opts) =>
hooks.reduce((prev, hook) => {
const next = hook(prev, opts)
if (next instanceof Promise) {
throw new Error('Cannot run async hooks in sync mode.')
}
return typeof next !== 'undefined' ? next : prev
}, value)
}
Expand Down
13 changes: 13 additions & 0 deletions packages/react-static/src/node/getWebpackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import getConfig from '../static/getConfig'
import { webpackConfig } from '../static/webpack'

// Required so handle static.config.js defined as es module
require('../utils/binHelper')

export default (function getWebpackConfig(configPath, stage = 'dev') {
return new Promise(resolve =>
getConfig(configPath, staticConfig =>
resolve(webpackConfig({ config: staticConfig, stage }))
)
)
})
10 changes: 10 additions & 0 deletions packages/react-static/src/node/getWebpackConfigSync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import getConfig from '../static/getConfig'
import { webpackConfig } from '../static/webpack'

// Required so handle static.config.js defined as es module
require('../utils/binHelper')

export default (function getWebpackConfigSync(configPath, stage = 'dev') {
const staticConfig = getConfig(configPath, undefined, { sync: true })
return webpackConfig({ config: staticConfig, stage, sync: true })
})
35 changes: 34 additions & 1 deletion packages/react-static/src/static/__tests__/getConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ describe('getConfig', () => {
let spyProcess

beforeEach(() => {
mockPlugin.mockReset()
spyProcess = jest.spyOn(process, 'cwd').mockImplementation(() => './root/')
})

Expand Down Expand Up @@ -133,14 +134,46 @@ describe('getConfig', () => {
})

it('should pass on plugin options to those plugins', async () => {
mockPlugin.mockReset()
// mapped by the moduleNameMapper in package.json -> src/static/__mocks__/configWithPluginWithOptions.mock.js
await getConfig('./path/to/configWithPluginWithOptions.mock.js')

expect(mockPlugin.mock.calls[0]).toEqual([{ mockOption: 'some-option' }])
})
})

describe('when called synchronously', () => {
it('should return after executing plugin hooks sync', () => {
mockPlugin.mockImplementation(() => ({
config: config => Object.assign(config, { syncPlugin: true }),
}))

const config = getConfig(undefined, undefined, { sync: true })
expect(config.syncPlugin).toBe(true)
})

it('should throw if plugin hooks execute async', () => {
mockPlugin.mockImplementation(() => ({
config: config => Promise.resolve(config),
}))

expect(() => getConfig(undefined, undefined, { sync: true })).toThrow(
'Cannot run async hooks in sync mode'
)
})
})

describe('when called asynchronously', () => {
it('should resolve after executing plugin hooks async', async () => {
mockPlugin.mockImplementation(() => ({
config: config =>
Promise.resolve(Object.assign(config, { syncPlugin: true })),
}))

const config = await getConfig()
expect(config.syncPlugin).toBe(true)
})
})

afterEach(() => {
spyProcess.mockRestore()
})
Expand Down
54 changes: 54 additions & 0 deletions packages/react-static/src/static/__tests__/webpack.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { webpackConfig } from '../webpack'
import staticConfig from '../__mocks__/defaultConfigDevelopment.mock'

const createConfigWithWebpackHook = hook => {
const hooks = { webpack: hook }
return Object.assign({}, staticConfig, { plugins: [{ hooks }] })
}

describe('webpack', () => {
describe('when called synchronously', () => {
it('should return after executing plugin hooks sync', () => {
const config = createConfigWithWebpackHook(wpConfig =>
Object.assign(wpConfig, { mode: 'development' })
)

const myWebpackConfig = webpackConfig({
config,
stage: 'prod',
sync: true,
})

expect(myWebpackConfig.mode).toBe('development')
})

it('should throw if plugin hooks execute async', () => {
const config = createConfigWithWebpackHook(wpConfig =>
Promise.resolve(Object.assign(wpConfig, { mode: 'development' }))
)

expect(() =>
webpackConfig({
config,
stage: 'prod',
sync: true,
})
).toThrow('Cannot run async hooks in sync mode')
})
})

describe('when called asynchronously', () => {
it('should resolve after executing plugin hooks async', async () => {
const config = createConfigWithWebpackHook(wpConfig =>
Promise.resolve(Object.assign(wpConfig, { mode: 'development' }))
)

const myWebpackConfig = await webpackConfig({
config,
stage: 'prod',
})

expect(myWebpackConfig.mode).toBe('development')
})
})
})
51 changes: 23 additions & 28 deletions packages/react-static/src/static/getConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DEFAULT_ROUTES = [{ path: '/' }]
const DEFAULT_ENTRY = 'index'
const DEFAULT_EXTENSIONS = ['.js', '.jsx']

export const buildConfig = async (config = {}) => {
export const buildConfig = (config = {}, { sync } = {}) => {
// path defaults
config.paths = {
root: nodePath.resolve(process.cwd()),
Expand Down Expand Up @@ -259,49 +259,44 @@ export const buildConfig = async (config = {}) => {

config.plugins = config.plugins.map(resolvePlugin)

const configHook = makeHookReducer(config.plugins, 'config')
config = await configHook(config)

return config
const configHook = makeHookReducer(config.plugins, 'config', { sync })
return configHook(config)
}

const buildConfigFromPath = async configPath => {
const buildConfigFromPath = (configPath, options) => {
delete require.cache[configPath]
const config = require(configPath).default
return buildConfig(config)
return buildConfig(config, options)
}

// Retrieves the static.config.js from the current project directory
export default (async function getConfig(
export default (function getConfig(
configPath = DEFAULT_PATH_FOR_STATIC_CONFIG,
subscription
subscription,
options = {}
) {
const resolvedPath = resolveModule(configPath)

const noConfig =
configPath === DEFAULT_PATH_FOR_STATIC_CONFIG && !resolvedPath

if (noConfig) {
if (subscription) {
return new Promise(async () => {
subscription(await buildConfig(defaultConfig))
})
}
return buildConfig(defaultConfig)
}

const config = await buildConfigFromPath(resolvedPath || configPath)
const executeBuildConfig = () =>
noConfig
? buildConfig(defaultConfig, options)
: buildConfigFromPath(resolvedPath || configPath, options)

if (subscription) {
// If subscribing, return a never ending promise
return new Promise(() => {
chokidar.watch(resolvedPath).on('all', async () => {
subscription(await buildConfigFromPath(resolvedPath))
})
})
if (!subscription) {
return executeBuildConfig()
}

return config
// If subscribing, return a never ending promise
// Note: All subscriptions will be handled async
return new Promise(async () =>
noConfig
? subscription(await executeBuildConfig())
: chokidar
.watch(resolvedPath)
.on('all', async () => subscription(await executeBuildConfig()))
)
})

function resolveModule(path, config) {
Expand Down
8 changes: 3 additions & 5 deletions packages/react-static/src/static/webpack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export { reloadRoutes }

// Builds a compiler using a stage preset, then allows extension via
// webpackConfigurator
export async function webpackConfig({ config, stage }) {
export function webpackConfig({ config, stage, sync }) {
let webpackConfig
if (stage === 'dev') {
webpackConfig = require('./webpack.config.dev').default({ config })
Expand All @@ -51,15 +51,13 @@ export async function webpackConfig({ config, stage }) {

const defaultLoaders = getStagedRules({ config, stage })

const webpackHook = makeHookReducer(config.plugins, 'webpack')
const webpackHook = makeHookReducer(config.plugins, 'webpack', { sync })

webpackConfig = await webpackHook(webpackConfig, {
return webpackHook(webpackConfig, {
config,
stage,
defaultLoaders,
})

return webpackConfig
}

// Starts the development server
Expand Down

0 comments on commit 280d7c0

Please sign in to comment.