| 1 |
/** |
| 2 |
* External Dependencies |
| 3 |
*/ |
| 4 |
const fs = require('fs'); |
| 5 |
const { resolve, relative, extname, join } = require('path'); |
| 6 |
const webpack = require('webpack'); |
| 7 |
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); |
| 8 |
const CopyWebpackPlugin = require('copy-webpack-plugin'); |
| 9 |
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); |
| 10 |
const defaultConfig = require('@wordpress/scripts/config/webpack.config'); |
| 11 |
|
| 12 |
const isProduction = process.env.NODE_ENV === 'production'; |
| 13 |
|
| 14 |
/** |
| 15 |
* Recursively find files by extension, skipping SCSS partials (prefixed with _). |
| 16 |
*/ |
| 17 |
function findFiles(dir, ext) { |
| 18 |
const results = []; |
| 19 |
if (!fs.existsSync(dir)) return results; |
| 20 |
const items = fs.readdirSync(dir, { withFileTypes: true }); |
| 21 |
for (const item of items) { |
| 22 |
const fullPath = join(dir, item.name); |
| 23 |
if (item.isDirectory()) { |
| 24 |
// Skip src/scss/components folder |
| 25 |
if (fullPath.includes('src/scss/components')) continue; |
| 26 |
results.push(...findFiles(fullPath, ext)); |
| 27 |
} else if (item.name.endsWith(ext) && !item.name.startsWith('_')) { |
| 28 |
results.push(fullPath); |
| 29 |
} |
| 30 |
} |
| 31 |
return results; |
| 32 |
} |
| 33 |
|
| 34 |
// Build entry map: srcDir -> outDir (without extension) |
| 35 |
function buildEntries(srcDir, ext, outDir) { |
| 36 |
const entries = {}; |
| 37 |
findFiles(resolve(__dirname, srcDir), ext).forEach((file) => { |
| 38 |
const rel = relative(resolve(__dirname, srcDir), file); |
| 39 |
const name = rel.replace(extname(rel), ''); |
| 40 |
entries[`${outDir}/${name}`] = file; |
| 41 |
}); |
| 42 |
return entries; |
| 43 |
} |
| 44 |
|
| 45 |
// src/js/**/*.js -> assets/js/**/*.js |
| 46 |
const jsEntries = buildEntries('src/js', '.js', 'js'); |
| 47 |
|
| 48 |
// src/scss/**/*.scss -> assets/css/**/*.css (partials excluded) |
| 49 |
const scssEntries = buildEntries('src/scss', '.scss', 'css'); |
| 50 |
|
| 51 |
// src/admin/dashboard.js -> assets/admin/admin.js (+ admin.css via style.scss import) |
| 52 |
const adminEntries = { |
| 53 |
'admin/admin': resolve(__dirname, 'src/admin/dashboard.js'), |
| 54 |
}; |
| 55 |
|
| 56 |
// src/admin/others/**/*.js -> assets/admin/others/**/*.js |
| 57 |
const adminOthersJsEntries = buildEntries('src/admin/others', '.js', 'admin/others'); |
| 58 |
|
| 59 |
// src/admin/others/scss/**/*.scss -> assets/admin/others/css/**/*.css |
| 60 |
const adminOthersScssEntries = (() => { |
| 61 |
const entries = {}; |
| 62 |
const base = resolve(__dirname, 'src/admin/others'); |
| 63 |
findFiles(base, '.scss').forEach((file) => { |
| 64 |
const rel = relative(base, file); |
| 65 |
const name = rel.replace(extname(rel), '').replace(/^scss([\\/])/, 'css$1'); |
| 66 |
entries[`admin/others/${name}`] = file; |
| 67 |
}); |
| 68 |
return entries; |
| 69 |
})(); |
| 70 |
|
| 71 |
// Keep default plugins except MiniCssExtractPlugin and CopyPlugin (we define our own) |
| 72 |
const filteredPlugins = defaultConfig.plugins.filter( |
| 73 |
(plugin) => |
| 74 |
!(plugin instanceof MiniCssExtractPlugin) && |
| 75 |
!(plugin.constructor && plugin.constructor.name === 'CopyPlugin') |
| 76 |
); |
| 77 |
|
| 78 |
// Remove the default SCSS rule — we supply our own with url resolution disabled |
| 79 |
const defaultScssRule = (defaultConfig.module?.rules || []).find( |
| 80 |
(rule) => rule && rule.test && rule.test.toString() === /\.(sc|sa)ss$/.toString() |
| 81 |
); |
| 82 |
const nonScssRules = (defaultConfig.module?.rules || []).filter((r) => r !== defaultScssRule); |
| 83 |
|
| 84 |
// Clone the default SCSS rule but set url:false on css-loader |
| 85 |
const scssRule = { |
| 86 |
...defaultScssRule, |
| 87 |
use: defaultScssRule.use.map((u) => { |
| 88 |
const loader = typeof u === 'string' ? u : u?.loader; |
| 89 |
if (loader && /[\\/]css-loader[\\/]/.test(loader)) { |
| 90 |
return { |
| 91 |
...(typeof u === 'string' ? { loader: u } : u), |
| 92 |
options: { ...(typeof u === 'object' ? u.options : {}), url: false }, |
| 93 |
}; |
| 94 |
} |
| 95 |
return u; |
| 96 |
}), |
| 97 |
}; |
| 98 |
|
| 99 |
const config = { |
| 100 |
...defaultConfig, |
| 101 |
|
| 102 |
entry: { |
| 103 |
...jsEntries, |
| 104 |
...scssEntries, |
| 105 |
...adminEntries, |
| 106 |
...adminOthersJsEntries, |
| 107 |
...adminOthersScssEntries, |
| 108 |
}, |
| 109 |
|
| 110 |
output: { |
| 111 |
path: resolve(__dirname, 'assets'), |
| 112 |
filename: '[name].js', |
| 113 |
publicPath: 'auto', |
| 114 |
chunkFilename: '[name].js', |
| 115 |
}, |
| 116 |
|
| 117 |
module: { |
| 118 |
...defaultConfig.module, |
| 119 |
rules: [...nonScssRules, scssRule], |
| 120 |
}, |
| 121 |
|
| 122 |
plugins: [ |
| 123 |
new RemoveEmptyScriptsPlugin(), |
| 124 |
new MiniCssExtractPlugin({ filename: '[name].css' }), |
| 125 |
new CopyWebpackPlugin({ |
| 126 |
patterns: [ |
| 127 |
{ from: 'src/fonts', to: 'fonts' }, |
| 128 |
{ from: 'src/images', to: 'images' }, |
| 129 |
{ from: 'src/vendor', to: 'vendor' }, |
| 130 |
], |
| 131 |
}), |
| 132 |
new webpack.DefinePlugin({ |
| 133 |
'process.env.NODE_ENV': JSON.stringify( |
| 134 |
isProduction ? 'production' : 'development' |
| 135 |
), |
| 136 |
}), |
| 137 |
...filteredPlugins.filter( |
| 138 |
(p) => p.constructor.name !== 'DependencyExtractionWebpackPlugin' |
| 139 |
), |
| 140 |
|
| 141 |
/* |
| 142 |
* Keep @wordpress/* externals but remove .asset.php files after build |
| 143 |
*/ |
| 144 |
new (require('@wordpress/dependency-extraction-webpack-plugin'))(), |
| 145 |
{ |
| 146 |
apply(compiler) { |
| 147 |
compiler.hooks.afterEmit.tapAsync('RemoveAssetPhp', (compilation, callback) => { |
| 148 |
const outputPath = compilation.outputOptions.path; |
| 149 |
findFiles(outputPath, '.asset.php').forEach((file) => { |
| 150 |
try { fs.unlinkSync(file); } catch (e) { /* ignore */ } |
| 151 |
}); |
| 152 |
callback(); |
| 153 |
}); |
| 154 |
}, |
| 155 |
}, |
| 156 |
], |
| 157 |
|
| 158 |
devtool: false, |
| 159 |
stats: 'minimal', |
| 160 |
}; |
| 161 |
|
| 162 |
if (!isProduction) { |
| 163 |
config.devServer = { |
| 164 |
...config.devServer, |
| 165 |
allowedHosts: 'all', |
| 166 |
}; |
| 167 |
} |
| 168 |
|
| 169 |
module.exports = config; |