Skip to content

Commit 7cfa146

Browse files
author
Callin Mullaney
committed
feat: copying component assets should account for any type of directory structure
1 parent 72a4929 commit 7cfa146

2 files changed

Lines changed: 80 additions & 21 deletions

File tree

config/webpack/plugins.js

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resolve, dirname } from 'path';
1+
import { resolve, dirname, relative } from 'path';
22
import webpack from 'webpack';
33
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
44
import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts';
@@ -61,7 +61,7 @@ const componentsSrcRoot = isSrcExists ? resolve(srcDir, 'components') : srcDir;
6161
* Component output root (where compiled component assets go):
6262
* - Drupal + src/: `components/…`
6363
* - Otherwise: `dist/components/…`
64-
* (Relative to `projectDir`; used by CopyPlugin’s `to:` path.)
64+
* (Relative to `projectDir`; used by CopyPlugins `to:` path.)
6565
* @type {string}
6666
*/
6767
const componentsOutRoot =
@@ -94,7 +94,9 @@ function getPatterns(filesMatcher) {
9494
parentDir === 'layout' || parentDir === 'foundation'
9595
? '/components/'
9696
: '/';
97+
9798
const filePath = file.split(/(foundation\/|components\/|layout\/)/)[2];
99+
98100
const to = isDrupal
99101
? `${projectPath}${consolidateDirs}${parentDir}/${filePath}`
100102
: `${projectPath}/dist/${parentDir}/${filePath}`;
@@ -112,63 +114,113 @@ const CopyTwigPlugin = isSrcExists
112114
? new CopyPlugin({ patterns: getPatterns(componentFilesPattern) })
113115
: false;
114116

117+
/* -------------------------------------------------------------------------- */
118+
/* COMPONENT & GLOBAL ASSETS */
119+
/* -------------------------------------------------------------------------- */
120+
121+
/**
122+
* Asset allow-list (extensions we consider "static assets" to mirror).
123+
* Extend to suit your project (e.g., add `pdf`, `txt`, `xml`, etc.).
124+
* NOTE: We purposefully exclude code-like files via the filter below.
125+
* @type {RegExp}
126+
*/
127+
const ASSET_EXT_RE =
128+
/\.(?:png|jpe?g|gif|svg|webp|avif|ico|bmp|heic|heif|mp4|webm|mp3|ogg|wav|aac|woff2?|ttf|otf|eot|json|webmanifest|manifest|pdf)$/i;
129+
130+
/**
131+
* Exclude code & tooling files (don’t mirror these).
132+
* @type {RegExp}
133+
*/
134+
const EXCLUDE_CODE_RE =
135+
/\.(?:jsx?|tsx?|mjs|cjs|vue|svelte|scss|sass|less|styl|css|map|twig|php|yml|yaml|md|markdown|story(?:book)?\.[jt]sx?|stories\.[jt]sx?|test\.[jt]sx?)$/i;
136+
115137
/**
116-
* CopyPlugin instance: copies **component-local assets** (images, fonts, SVGs, etc.)
138+
* Shared filter for CopyPlugin patterns.
139+
* Decides whether a file should be copied as a "static asset".
117140
*
118-
* Example:
141+
* @param {string} resourcePath - Absolute file path on disk.
142+
* @param {string} base - The context directory for the pattern.
143+
* @returns {boolean} True if we should copy the file.
144+
*/
145+
const assetFilter = (resourcePath, base) => {
146+
const rel = relative(base, resourcePath);
147+
// Guard: stay inside context
148+
if (rel.startsWith('..')) return false;
149+
// Exclude typical code/tooling files
150+
if (EXCLUDE_CODE_RE.test(rel)) return false;
151+
// Include known asset extensions
152+
return ASSET_EXT_RE.test(rel);
153+
};
154+
155+
/**
156+
* Copy **all static assets inside components**, regardless of folder labels.
157+
*
158+
* Examples (all preserved under the component’s output root):
119159
* src/components/accordion/assets/dropdown-icon.svg
120-
* -> (Drupal+src) components/accordion/assets/dropdown-icon.svg
121-
* -> (WP/legacy) dist/components/accordion/assets/dropdown-icon.svg
160+
* src/components/accordion/images/icons/chevron.svg
161+
* src/components/accordion/icon.svg (root-level asset)
122162
*
123-
* Notes:
124-
* - `context` is set to the component source root so `[path]` starts **after**
125-
* `…/components/`.
126-
* - `to` uses `[path][name][ext]` to mirror the original tree.
127-
* - `noErrorOnMissing` avoids errors if no assets are present.
128163
* @type {CopyPlugin}
129164
*/
130165
const CopyComponentAssetsPlugin = new CopyPlugin({
131166
patterns: [
132167
{
133-
from: '**/assets/**/*',
168+
// Start at the components root and evaluate every file
169+
from: '**/*',
134170
context: componentsSrcRoot,
135171
to: resolve(projectDir, componentsOutRoot, '[path][name][ext]'),
136172
noErrorOnMissing: true,
137173
globOptions: {
138174
dot: false,
139-
ignore: ['**/.DS_Store', '**/Thumbs.db'],
175+
ignore: [
176+
'**/.DS_Store',
177+
'**/Thumbs.db',
178+
'**/node_modules/**',
179+
'**/dist/**',
180+
],
140181
},
182+
// Only copy files that match our asset allow-list and are not code
183+
filter: (resourcePath) => assetFilter(resourcePath, componentsSrcRoot),
141184
},
142185
],
143186
});
144187

145188
/**
146-
* CopyPlugin instance for **global (non-component) assets** that live
147-
* under `src/` but *outside* `src/components/`.
148-
*
149-
* These are mirrored under `dist/global/…` (because your base SCSS/JS already
150-
* use the `dist/global` convention).
189+
* OPTIONAL: Copy **global (non-component) assets** that live under `src/`
190+
* but outside `src/components/` (e.g. layout/site assets).
151191
*
192+
* Mirrors them under `dist/global/…`.
152193
* Disabled when there is no `src/` directory.
194+
*
153195
* @type {CopyPlugin|false}
154196
*/
155197
const CopyGlobalAssetsPlugin = isSrcExists
156198
? new CopyPlugin({
157199
patterns: [
158200
{
159-
from: '!(components|util)/**/assets/**/*',
201+
from: '!(components|util)/**/*',
160202
context: srcDir,
161203
to: resolve(projectDir, 'dist', 'global', '[path][name][ext]'),
162204
noErrorOnMissing: true,
163205
globOptions: {
164206
dot: false,
165-
ignore: ['**/.DS_Store', '**/Thumbs.db'],
207+
ignore: [
208+
'**/.DS_Store',
209+
'**/Thumbs.db',
210+
'**/node_modules/**',
211+
'**/dist/**',
212+
],
166213
},
214+
filter: (resourcePath) => assetFilter(resourcePath, srcDir),
167215
},
168216
],
169217
})
170218
: false;
171219

220+
/* -------------------------------------------------------------------------- */
221+
/* OTHER PLUGINS */
222+
/* -------------------------------------------------------------------------- */
223+
172224
/**
173225
* CleanWebpackPlugin configuration.
174226
* Wipes out compiled CSS/JS in `distPath` before a build; keeps images.

config/webpack/resolves.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
* - Exposes a Webpack-style `resolve.alias` object for `.twig` files
55
*/
66

7-
import { basename, resolve, relative, isAbsolute, join, path } from 'path';
7+
import {
8+
basename,
9+
resolve,
10+
relative,
11+
isAbsolute,
12+
join,
13+
posix as path,
14+
} from 'node:path';
815
import { sync as globSync } from 'glob';
916
import fs from 'fs-extra';
1017
import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };

0 commit comments

Comments
 (0)