Skip to content

Instantly share code, notes, and snippets.

@PikachuEXE
Created March 18, 2019 08:02
Show Gist options
  • Save PikachuEXE/ed580e37284cda54c86ba1aea0b79edd to your computer and use it in GitHub Desktop.
Save PikachuEXE/ed580e37284cda54c86ba1aea0b79edd to your computer and use it in GitHub Desktop.
Webpacker 4 multiple target config
/* eslint-disable global-require */
/* eslint-env node */
const validEnv = ["development", "test", "production"]
const currentEnv = process.env.NODE_ENV || "unknown"
const isDevelopmentEnv = currentEnv === "development"
const isProductionEnv = currentEnv === "production"
const isTestEnv = currentEnv === "test"
if (!validEnv.includes(currentEnv)) {
throw new Error(
"Please specify a valid `NODE_ENV` or " +
"`BABEL_ENV` environment variables. Valid values are \"development\", " +
"\"test\", and \"production\". Instead, received: " +
JSON.stringify(currentEnv) +
"."
)
}
module.exports = (targets, extra_options={}) => {
const generated_options = {
babelrc: false,
presets: [
isTestEnv && [
require("@babel/preset-env").default,
{
targets: {
node: "current",
},
},
],
(isProductionEnv || isDevelopmentEnv) && [
require("@babel/preset-env").default,
{
modules: false,
targets: targets,
forceAllTransforms: false,
useBuiltIns: false,
exclude: ["transform-typeof-symbol"],
// We are specifying mutiple browser target sets
ignoreBrowserslistConfig: true,
},
],
"@babel/preset-flow",
].filter(Boolean),
plugins: [
// require("babel-plugin-macros"),
require("@babel/plugin-syntax-dynamic-import").default,
isTestEnv && require("babel-plugin-dynamic-import-node"),
require("@babel/plugin-transform-exponentiation-operator").default,
require("@babel/plugin-transform-destructuring").default,
[
require("@babel/plugin-proposal-class-properties").default,
{
loose: true,
},
],
[
require("@babel/plugin-proposal-object-rest-spread").default,
{
useBuiltIns: true,
},
],
[
require("@babel/plugin-transform-runtime").default,
{
helpers: false,
regenerator: true,
},
],
[
require("@babel/plugin-transform-regenerator").default,
{
async: false,
},
],
"syntax-trailing-function-commas",
].filter(Boolean),
}
return {
loader: "babel-loader",
options: Object.assign({}, generated_options, extra_options),
}
}
/* eslint-env node */
process.env.NODE_ENV = process.env.NODE_ENV || "development"
const es5_environment = require("./es5_environment")
const es2015_environment = require("./es2015_environment")
module.exports = [
es5_environment,
es2015_environment,
]
const { environment } = require("@rails/webpacker")
// Since we have multiple targets
// And webpacker only assume one manifest file
// We need to enable "merging"
// https://github.com/webdeveric/webpack-assets-manifest
const WebpackAssetsManifest = require("webpack-assets-manifest")
const webpack_assets_manifest_plugin_options = Object.assign({}, environment.plugins.get("Manifest").options)
// This is for sharing data for same plugin in 2 targets
const new_manifest_assets_date_store = Object.create(null)
webpack_assets_manifest_plugin_options.assets = new_manifest_assets_date_store
const new_webpack_assets_manifest_plugin = new WebpackAssetsManifest(webpack_assets_manifest_plugin_options)
const oldSetRaw = new_webpack_assets_manifest_plugin.setRaw
new_webpack_assets_manifest_plugin.setRaw = function(key, value) {
const self = new_webpack_assets_manifest_plugin
const entrypointsKey = self.options.entrypointsKey
if (key === entrypointsKey && typeof entrypointsKey === "string") {
const existing_entrypoints = self.get(entrypointsKey)
if (typeof existing_entrypoints === "object" && existing_entrypoints != null) {
const merged_entrypoints = Object.assign({}, existing_entrypoints || {}, value)
oldSetRaw.call(self, key, merged_entrypoints)
return
}
}
oldSetRaw.call(self, key, value)
}
environment.plugins.append("Manifest", new_webpack_assets_manifest_plugin)
module.exports = {
environment: environment,
}
/* eslint-env node */
module.exports = {
test: /\.erb$/,
enforce: "pre",
exclude: /node_modules/,
use: [{
loader: "rails-erb-loader",
options: {
runner: (/^win/.test(process.platform) ? "ruby " : "") + "bin/rails runner",
env: {
...process.env,
DISABLE_SPRING: 1,
RAILS_CONFIG_EAGER_LOAD_ENABLED_IN_DEVELOPMENT: "false",
},
},
}],
}
/* eslint-disable global-require */
/* eslint-env node */
const COMMON_PREFIX = "es2015"
const { environment } = require("./environment_base")
const { join, resolve } = require("path")
const { cache_path: cachePath, source_path: sourcePath, resolved_paths: resolvedPaths } = require("@rails/webpacker/package/config")
const { nodeEnv } = require("@rails/webpacker/package/env")
// Dup config object
const ConfigObject = require("@rails/webpacker/package/config_types/config_object")
const custom_config = new ConfigObject(environment.toWebpackConfig().toObject())
// Since toObject is just shallow copy
custom_config.entry = new ConfigObject(custom_config.entry).toObject()
// Clone rules array
custom_config.module = Object.assign({}, custom_config.module, {rules: [].concat(custom_config.module.rules)})
const erb_loader = require("./loaders/erb")
const tpl_loader = require("./loaders/tpl")
// region Custom Entry Names
for (const [key, value] of Object.entries(custom_config.entry)) {
// Set new key (add prefix) and remove old key
custom_config.entry[`${COMMON_PREFIX}/${key}`] = value
delete custom_config.entry[key]
}
// endregion Custom Entry Names
const build_babel_loader = require("./loaders/babel_loader_builder")
const babel_loader = build_babel_loader(
{
esmodules: true,
},
{
cacheDirectory: join(cachePath, "babel-loader-node-modules"),
cacheCompression: nodeEnv === "production",
compact: nodeEnv === "production",
},
)
const rules_without_default_babel = [].concat(custom_config.module.rules)
.filter((r) => (!("test" in r)) || r.test.toString() !== /\.(js|jsx|mjs)?(\.erb)?$/.toString())
custom_config.module.rules = rules_without_default_babel.concat([
{
test: /\.es6$/,
enforce: "pre",
exclude: /node_modules/,
use: babel_loader,
},
erb_loader,
tpl_loader,
{
test: /\.(js|jsx|mjs)?(\.erb)?$/,
include: [sourcePath, ...resolvedPaths].map(p => resolve(p)),
exclude: /node_modules/,
use: babel_loader,
},
])
const FREQUENTLY_UPDATED_VENDORS = [
"braintree-web",
"highcharts",
"intl-tel-input",
"libphonenumber-js",
"dropzone",
"flickity",
"select2",
"noty",
"intro.js",
]
const POLYFILL_PATHS = [
"core-js",
"object.entries",
"object.values",
"classlist.js",
"element-closest",
"events-polyfill",
]
const FREQUENTLY_UPDATED_VENDORS_REGEX =
new RegExp(`[\\/]node_modules[\\/](${FREQUENTLY_UPDATED_VENDORS.join("|")})[\\/]`, "i")
const POLYFILL_PATHS_REGEX =
new RegExp(`[\\/]node_modules[\\/](${POLYFILL_PATHS.join("|")})[\\/]`, "i")
custom_config.merge({
optimization: {
runtimeChunk: {
name: `${COMMON_PREFIX}/apps/spacious_website/runtime`,
},
splitChunks: {
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
// Extract it no matter how small is it
minSize: 0,
// It's fine to have many init requests
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
maxInitialRequests: Infinity,
cacheGroups: {
vendors: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors`,
test: (module, _chunks) => {
if (module.context == null) { return false }
if (FREQUENTLY_UPDATED_VENDORS_REGEX.test(module.context)) { return false }
if (POLYFILL_PATHS_REGEX.test(module.context)) { return false }
return module.context.includes("node_modules")
},
priority: -10,
chunks: "initial",
},
frequently_updated_vendors: {
// function taken from
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
// npm package names are URL-safe, but some servers don't like @ symbols
return `${COMMON_PREFIX}/apps/spacious_website/vendors/npm.${packageName.replace("@", "")}`
},
test: FREQUENTLY_UPDATED_VENDORS_REGEX,
priority: -9,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks: {
name(module) {
const chunk_name = module.context.match(/app[\\/]assets[\\/]javascripts[\\/](.*?)([\\/]|$)/)[1]
return `${COMMON_PREFIX}/apps/spacious_website/chunks/${chunk_name}`
},
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/") &&
(!module.context.includes("\n")) &&
(!/widgets|application/.test(module.context))
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_concepts_listings: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/concepts/listings`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/concepts/listings")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_plugins_i18n_js: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/plugins/i18n-js`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/plugins/i18n-js")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_plugins_js_routes: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/plugins/js-routes`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/plugins/js-routes")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_polyfills: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/polyfills`,
test: (module, _chunks) => {
if (module.resource == null) { return false }
if (POLYFILL_PATHS_REGEX.test(module.resource)) { return true }
return module.resource.includes("apps/spacious_website/polyfills/optional_content")
},
priority: 1,
chunks: "async",
reuseExistingChunk: false,
},
apps_spacious_website_vendor_large_libs: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/large_libs`,
test: (module, _chunks) => {
if (module.context == null) { return false }
return module.context.includes("node_modules") &&
/bluebird|raven-js|urijs/.test(module.context)
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_vendor_jquery: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/jquery`,
test: (module, _chunks) => {
if (module.context == null) { return false }
return module.context.includes("node_modules") &&
/jquery|typeahead|timepicker/.test(module.context)
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_vendor_leaflet: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/leaflet`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Includes our leaflet plugins too
// (they were left in default chunk)
if (module.context.includes("pseudo_plugins/leaflet_plugins")) { return true }
return module.context.includes("node_modules") && module.context.includes("leaflet")
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
default: {
name: `${COMMON_PREFIX}/apps/spacious_website/default`,
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
})
const webpack_config = custom_config.merge({
output: {
filename: `js/${COMMON_PREFIX}/[name]-[chunkhash].js`,
chunkFilename: `js/${COMMON_PREFIX}/[name]-[chunkhash].chunk.js`,
hotUpdateChunkFilename: `js/${COMMON_PREFIX}/[id]-[hash].hot-update.js`,
},
})
module.exports = webpack_config
/* eslint-disable global-require */
/* eslint-env node */
const COMMON_PREFIX = "es5"
const { environment } = require("./environment_base")
const { join, resolve } = require("path")
const { cache_path: cachePath, source_path: sourcePath, resolved_paths: resolvedPaths } = require("@rails/webpacker/package/config")
const { nodeEnv } = require("@rails/webpacker/package/env")
// Dup config object
const ConfigObject = require("@rails/webpacker/package/config_types/config_object")
const custom_config = new ConfigObject(environment.toWebpackConfig().toObject())
// Since toObject is just shallow copy
custom_config.entry = new ConfigObject(custom_config.entry).toObject()
// Clone rules array
custom_config.module = Object.assign({}, custom_config.module, {rules: [].concat(custom_config.module.rules)})
const erb_loader = require("./loaders/erb")
const tpl_loader = require("./loaders/tpl")
// region Custom Entry Names
for (const [key, value] of Object.entries(custom_config.entry)) {
// Set new key (add prefix) and remove old key
custom_config.entry[`${COMMON_PREFIX}/${key}`] = value
delete custom_config.entry[key]
}
// endregion Custom Entry Names
const build_babel_loader = require("./loaders/babel_loader_builder")
const babel_loader = build_babel_loader(
{
browsers: [
"> 1%",
"Safari >= 9",
"iOS >= 8",
"Firefox >= 52",
"Edge >= 13",
"ie >= 11",
],
},
{
cacheDirectory: join(cachePath, "babel-loader-node-modules"),
cacheCompression: nodeEnv === "production",
compact: nodeEnv === "production",
},
)
const rules_without_default_babel = [].concat(custom_config.module.rules)
.filter((r) => (!("test" in r)) || r.test.toString() !== /\.(js|jsx|mjs)?(\.erb)?$/.toString())
custom_config.module.rules = rules_without_default_babel.concat([
{
test: /\.es6$/,
enforce: "pre",
exclude: /node_modules/,
use: babel_loader,
},
erb_loader,
tpl_loader,
{
test: /\.(js|jsx|mjs)?(\.erb)?$/,
include: [sourcePath, ...resolvedPaths].map(p => resolve(p)),
exclude: /node_modules/,
use: babel_loader,
},
])
const FREQUENTLY_UPDATED_VENDORS = [
"braintree-web",
"highcharts",
"intl-tel-input",
"libphonenumber-js",
"dropzone",
"flickity",
"select2",
"noty",
"intro.js",
]
const POLYFILL_PATHS = [
"core-js",
"object.entries",
"object.values",
"classlist.js",
"element-closest",
"events-polyfill",
]
const FREQUENTLY_UPDATED_VENDORS_REGEX =
new RegExp(`[\\/]node_modules[\\/](${FREQUENTLY_UPDATED_VENDORS.join("|")})[\\/]`, "i")
const POLYFILL_PATHS_REGEX =
new RegExp(`[\\/]node_modules[\\/](${POLYFILL_PATHS.join("|")})[\\/]`, "i")
custom_config.merge({
optimization: {
runtimeChunk: {
name: `${COMMON_PREFIX}/apps/spacious_website/runtime`,
},
splitChunks: {
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
// Extract it no matter how small is it
minSize: 0,
// It's fine to have many init requests
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
maxInitialRequests: Infinity,
cacheGroups: {
vendors: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors`,
test: (module, _chunks) => {
if (module.context == null) { return false }
if (FREQUENTLY_UPDATED_VENDORS_REGEX.test(module.context)) { return false }
if (POLYFILL_PATHS_REGEX.test(module.context)) { return false }
return module.context.includes("node_modules")
},
priority: -10,
chunks: "initial",
},
frequently_updated_vendors: {
// function taken from
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
// npm package names are URL-safe, but some servers don't like @ symbols
return `${COMMON_PREFIX}/apps/spacious_website/vendors/npm.${packageName.replace("@", "")}`
},
test: FREQUENTLY_UPDATED_VENDORS_REGEX,
priority: -9,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks: {
name(module) {
const chunk_name = module.context.match(/app[\\/]assets[\\/]javascripts[\\/](.*?)([\\/]|$)/)[1]
return `${COMMON_PREFIX}/apps/spacious_website/chunks/${chunk_name}`
},
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/") &&
(!module.context.includes("\n")) &&
(!/widgets|application/.test(module.context))
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_concepts_listings: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/concepts/listings`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/concepts/listings")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_plugins_i18n_js: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/plugins/i18n-js`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/plugins/i18n-js")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_chunks_plugins_js_routes: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/plugins/js-routes`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Some chunks are too small to be worth splitting
return module.context.includes("app/assets/javascripts/plugins/js-routes")
},
priority: 1,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_polyfills: {
name: `${COMMON_PREFIX}/apps/spacious_website/chunks/polyfills`,
test: (module, _chunks) => {
if (module.resource == null) { return false }
if (POLYFILL_PATHS_REGEX.test(module.resource)) { return true }
return module.resource.includes("apps/spacious_website/polyfills/optional_content")
},
priority: 1,
chunks: "async",
reuseExistingChunk: false,
},
apps_spacious_website_vendor_large_libs: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/large_libs`,
test: (module, _chunks) => {
if (module.context == null) { return false }
return module.context.includes("node_modules") &&
/bluebird|raven-js|urijs/.test(module.context)
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_vendor_jquery: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/jquery`,
test: (module, _chunks) => {
if (module.context == null) { return false }
return module.context.includes("node_modules") &&
/jquery|typeahead|timepicker/.test(module.context)
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
apps_spacious_website_vendor_leaflet: {
name: `${COMMON_PREFIX}/apps/spacious_website/vendors/leaflet`,
test: (module, _chunks) => {
if (module.context == null) { return false }
// Includes our leaflet plugins too
// (they were left in default chunk)
if (module.context.includes("pseudo_plugins/leaflet_plugins")) { return true }
return module.context.includes("node_modules") && module.context.includes("leaflet")
},
priority: 0,
// Providing all can be particularly powerful,
// because it means that chunks can be shared even between async and non-async chunks.
chunks: "all",
},
default: {
name: `${COMMON_PREFIX}/apps/spacious_website/default`,
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
})
const webpack_config = custom_config.merge({
output: {
filename: `js/${COMMON_PREFIX}/[name]-[chunkhash].js`,
chunkFilename: `js/${COMMON_PREFIX}/[name]-[chunkhash].chunk.js`,
hotUpdateChunkFilename: `js/${COMMON_PREFIX}/[id]-[hash].hot-update.js`,
},
})
module.exports = webpack_config
module.exports = {
test: /\.html\._tpl$/,
enforce: "pre",
exclude: /node_modules/,
use: [{
loader: "raw-loader",
}],
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment