@rails/webpacker
Version:
Use webpack to manage app-like JavaScript modules in Rails
177 lines (153 loc) • 4.89 kB
JavaScript
/* eslint global-require: 0 */
/* eslint import/no-dynamic-require: 0 */
const {
basename, dirname, join, relative, resolve
} = require('path')
const { sync } = require('glob')
const extname = require('path-complete-extname')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const WebpackAssetsManifest = require('webpack-assets-manifest')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const PnpWebpackPlugin = require('pnp-webpack-plugin')
const { isNotObject, prettyPrint } = require('../utils/helpers')
const deepMerge = require('../utils/deep_merge')
const { ConfigList, ConfigObject } = require('../config_types')
const rules = require('../rules')
const config = require('../config')
const getLoaderList = () => {
const result = new ConfigList()
Object.keys(rules).forEach((key) => result.append(key, rules[key]))
return result
}
const getPluginList = () => {
const result = new ConfigList()
result.append(
'Environment',
new webpack.EnvironmentPlugin(process.env)
)
result.append('CaseSensitivePaths', new CaseSensitivePathsPlugin())
result.append(
'MiniCssExtract',
new MiniCssExtractPlugin({
filename: 'css/[name]-[contenthash:8].css',
chunkFilename: 'css/[name]-[contenthash:8].chunk.css'
})
)
result.append(
'Manifest',
new WebpackAssetsManifest({
entrypoints: true,
writeToDisk: true,
publicPath: config.publicPathWithoutCDN
})
)
return result
}
const getExtensionsGlob = () => {
const { extensions } = config
return extensions.length === 1 ? `**/*${extensions[0]}` : `**/*{${extensions.join(',')}}`
}
const getEntryObject = () => {
const result = new ConfigObject()
const glob = getExtensionsGlob()
const rootPath = join(config.source_path, config.source_entry_path)
const paths = sync(join(rootPath, glob))
paths.forEach((path) => {
const namespace = relative(join(rootPath), dirname(path))
const name = join(namespace, basename(path, extname(path)))
let assetPaths = resolve(path)
// Allows for multiple filetypes per entry (https://webpack.js.org/guides/entry-advanced/)
// Transforms the config object value to an array with all values under the same name
let previousPaths = result.get(name)
if (previousPaths) {
previousPaths = Array.isArray(previousPaths) ? previousPaths : [previousPaths]
previousPaths.push(assetPaths)
assetPaths = previousPaths
}
result.set(name, assetPaths)
})
return result
}
const getModulePaths = () => {
const result = new ConfigList()
result.append('source', resolve(config.source_path))
if (config.additional_paths) {
config.additional_paths.forEach((path) => result.append(path, resolve(path)))
}
result.append('node_modules', 'node_modules')
return result
}
const getBaseConfig = () => new ConfigObject({
mode: 'production',
output: {
filename: 'js/[name]-[contenthash].js',
chunkFilename: 'js/[name]-[contenthash].chunk.js',
hotUpdateChunkFilename: 'js/[id]-[hash].hot-update.js',
path: config.outputPath,
publicPath: config.publicPath
},
resolve: {
extensions: config.extensions,
plugins: [PnpWebpackPlugin]
},
resolveLoader: {
modules: ['node_modules'],
plugins: [PnpWebpackPlugin.moduleLoader(module)]
},
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
})
module.exports = class Base {
constructor() {
this.loaders = getLoaderList()
this.plugins = getPluginList()
this.config = getBaseConfig()
this.entry = getEntryObject()
this.resolvedModules = getModulePaths()
}
splitChunks(callback = null) {
let appConfig = {}
const defaultConfig = {
optimization: {
// Split vendor and common chunks
// https://twitter.com/wSokra/status/969633336732905474
splitChunks: {
chunks: 'all',
name: true
},
// Separate runtime chunk to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true
}
}
if (callback) {
appConfig = callback(defaultConfig)
if (isNotObject(appConfig)) {
throw new Error(`
${prettyPrint(appConfig)} is not a valid splitChunks configuration.
See https://webpack.js.org/plugins/split-chunks-plugin/#configuration
`)
}
}
return this.config.merge(deepMerge(defaultConfig, appConfig))
}
toWebpackConfig() {
return this.config.merge({
entry: this.entry.toObject(),
module: {
strictExportPresence: true,
rules: this.loaders.values()
},
plugins: this.plugins.values(),
resolve: {
modules: this.resolvedModules.values()
}
})
}
}