Skip to content
Snippets Groups Projects
Commit 42b9aa23 authored by Michael Lander's avatar Michael Lander
Browse files

Issue #3380061 by michaellander: Replace pluggables concept with webpack...

Issue #3380061 by michaellander: Replace pluggables concept with webpack minsplits and automatic dependencies
parent 12cf5027
No related branches found
No related tags found
1 merge request!10Issue #3380061 by michaellander: Replace pluggables concept with webpack...
const write_yaml = require('write-yaml');
const merge = require('lodash.merge');
const _set = require('lodash.set');
const glob = require('glob');
const path = require('path');
const fs = require('fs');
const { libraryGlobList, excludedDirs } = require('../kinetic.config');
/**
* Construct library definition for partials.yml
* @param {string} chunkName
* @param {string} importName
* @returns {{}}
*/
function generateLibraryFromEntryPoint(chunkName, importName) {
const definition = {};
const pathArr = importName.split('/');
const libName = pathArr.slice(pathArr.indexOf('source') + 1, -1);
// libName.splice(0, 1);
// libName.unshift(libName[0].replace(/[^a-z]/ig, ''));
const entry = libName.join('--').replace(/^\d+-/ig, '');
definition[entry] = {};
let ext = pathArr[pathArr.length - 1].split('.').pop();
_set(definition[entry], 'dependencies', ['kinetic/kinetic']);
if (ext === 'scss') {
_set(definition[entry], `css.theme['${chunkName}.css']`, {});
}
if (ext === 'js') {
_set(definition[entry], `js['${chunkName}.js']`, {});
}
// Search for a config.js file.
try {
const confFilePath = path.resolve(importName, '../config.js');
if (fs.existsSync(confFilePath)) {
const config = require(confFilePath);
if (Object.prototype.hasOwnProperty.call(config, 'dependencies')) {
config.dependencies.push('kinetic/kinetic');
}
// Merge in the config js. does not support attributes yet.
definition[entry] = merge(definition[entry], config);
}
} catch(err) {
console.error(err);
}
return definition;
}
/**
* Construct entry points for partial libraries.
* @returns {{}}
*/
function generatePartialsEntries() {
const entryPoints = {};
libraryGlobList.forEach((directory) => {
const globRoot = path.resolve(__dirname, `../${directory}`);
// Collect all scss, js files from the configured directory.
// Add in any other file extensions (jsx, vue) here too.
glob.sync('**/*.{scss,js}', {
cwd: globRoot
})
.forEach((filePath) => {
// Entry point is the name of the SDC directory.
const pathArr = filePath.split('/');
// Check if this file should be excluded from being an entry point.
if (pathArr.some((name) => excludedDirs.indexOf(name) !== -1)) {
return;
}
const filename = pathArr[pathArr.length - 1].split('.');
const absFilePath = path.resolve(globRoot, filePath);
let fileExtension = filename.pop();
// Add an entry point for scss files.
if (fileExtension === 'scss') {
entryPoints[`dist/css/${filename[0]}`] = absFilePath;
}
// If using react or vue, check for jsx or vue file extension.
// Ignore config becasue of config.js files that may also appear.
if (fileExtension === 'js' && filename[0] !== 'config') {
entryPoints[`dist/js/${filename[0]}`] = absFilePath;
}
});
});
console.log('Created partials entry points');
return entryPoints;
}
/**
* Create the partials.yml file.
*/
function createPartialsYaml(entryPoints) {
let libraries = {};
Object.keys(entryPoints).forEach((entry) => {
// Create the drupal library definition from the webpack entry point.
const lib = generateLibraryFromEntryPoint(entry, entryPoints[entry]);
const libName = Object.keys(lib)[0];
libraries[libName] = merge(libraries[libName], lib[libName]);
});
// Write out file.
write_yaml('partials.yml', libraries, (err) => {
console.log('Generating partials.yml');
if (err) {
console.error('ERROR: Could not generate partials.yml');
}
});
}
module.exports = { generatePartialsEntries, createPartialsYaml };
const glob = require('glob');
const path = require('path');
const { excludedDirs } = require('../kinetic.config');
/**
* Create the webpack entrypoints for SDC. The 02-components directory should only contain SDCs
*/
const sdcEntries = {};
const componentDirName = '02-components';
const sdcSource = path.resolve('source', componentDirName);
// Get all the SCSS and dev.js files in the SDC directory.
glob.sync('**/*.{scss,es6.js}', {
cwd: sdcSource,
})
.forEach((path) => {
// Entry point is the name of the SDC directory.
const pathArr = path.split('/');
// Check if this file should be excluded from being an entry point.
if (pathArr.some((name) => excludedDirs.indexOf(name) !== -1)) {
return;
}
const sdcDirname = pathArr[pathArr.length - 2];
pathArr.pop();
const entryChunkName = `source/${componentDirName}/${pathArr.join('/')}/${sdcDirname}`;
const importName = `./source/${componentDirName}/${path}`;
// We must create an array of files that map to a single chunkName. This is to avoid duplicate chunk names
// and webpack will throw an error. Ex. { '/button' : ['/button/button.js', '/button/button.css'] }
// This will not work: { '/button': '/button/button.js', '/button': '/button/button.css' }
if (entryChunkName in sdcEntries) {
sdcEntries[entryChunkName].push(importName);
} else {
sdcEntries[entryChunkName] = [importName];
}
});
console.log('Created entry points for SDCs');
module.exports = sdcEntries;
const glob = require('glob');
const path = require('path');
const fs = require("fs")
class kineticLibraries {
constructor() {
this.libraries = {};
}
addLibrary(library) {
if(this.libraries.hasOwnProperty(library.libraryPath + '|' + library.libraryName)) {
throw new Error('Library already exists.');
}
this.libraries[library.libraryPath + '|' + library.libraryName] = library;
return this;
}
getLibrary(libraryPath, libraryName) {
return this.libraries[libraryPath + '|' + libraryName];
}
getLibraryFromEntryPoint(destination) {
for (const libraryName in this.libraries) {
for (const entryPointSource in this.libraries[libraryName].entryPoints) {
// If source matches entry point source before the '.'.
if (destination === this.libraries[libraryName].entryPoints[entryPointSource]) {
return this.libraries[libraryName];
}
}
}
return false;
}
getLibraries() {
return this.libraries;
}
getEntryPointsList() {
let entryPointList = {};
for (const libraryName in this.libraries) {
for (const entryPointSource in this.libraries[libraryName].entryPoints) {
// If already exists, turn into an array.
if (!entryPointList.hasOwnProperty(this.libraries[libraryName].entryPoints[entryPointSource])) {
entryPointList[this.libraries[libraryName].entryPoints[entryPointSource]] = [entryPointSource];
}
else {
entryPointList[this.libraries[libraryName].entryPoints[entryPointSource]].push(entryPointSource);
}
}
}
return entryPointList;
}
}
class kineticLibrary {
entryPoints = {};
dependencies = [];
constructor(libraryPath, libraryName) {
this.libraryPath = libraryPath;
this.libraryName = libraryName;
}
addEntryPoint(destination, source, validate = true) {
this.entryPoints[source] = destination;
return this;
}
hasDependency(libraryName) {
return this.dependencies.indexOf(libraryName) !== -1;
}
addDependency(libraryName) {
if (!this.hasDependency(libraryName)) {
this.dependencies.push(libraryName);
}
return this;
}
getDependencies() {
return this.dependencies;
}
hasDependencies() {
return this.dependencies.length > 0;
}
getDependencyIndex() {
return [this.libraryName, 'dependencies'];
}
}
class kineticComponentLibrary extends kineticLibrary {
constructor(libraryPath, libraryName) {
super(libraryPath, libraryName);
}
getDependencyIndex() {
return ['libraryOverrides', 'dependencies'];
}
}
let librariesInstance = new kineticLibraries();
librariesInstance.addLibrary(
new kineticLibrary(
'kinetic.libraries.yml',
'kinetic',
).addEntryPoint(
'dist/js/index',
'./source/01-base/global/js/index.js',
).addEntryPoint(
'dist/css/index',
'./source/01-base/global/scss/index.scss',
).addEntryPoint(
'dist/css/utilities',
'./source/01-base/global/scss/utilities.scss',
).addEntryPoint(
'dist/css/wysiwyg',
'./source/01-base/global/scss/wysiwyg.scss',
)
);
const componentDirName = '02-components';
const sdcSource = path.resolve('source', componentDirName);
// Get all the SCSS and dev.js files in the SDC directory.
glob.sync('**/*.{scss,es6.js}', {
cwd: sdcSource,
})
.forEach((assetPath) => {
// Entry point is the name of the SDC directory.
const pathArr = assetPath.split('/');
const componentName = pathArr.slice(-2)[0];
const componentRelativePath = pathArr.slice(0, -1).join('/');
const componentPath = `source/${componentDirName}/${componentRelativePath}`;
const componentYmlPath = `${componentPath}/${componentName}.component.yml`;
// Only process entry points that are known Drupal components.
if (fs.existsSync(componentYmlPath)) {
let componentLibrary = librariesInstance.getLibrary(componentYmlPath, componentName);
if (!componentLibrary) {
componentLibrary = new kineticComponentLibrary(
componentYmlPath,
componentName,
);
librariesInstance.addLibrary(componentLibrary);
}
componentLibrary.addEntryPoint(
`${componentPath}/${componentName}`,
`./source/${componentDirName}/${assetPath}`,
);
}
});
module.exports = librariesInstance;
const write_yaml = require('write-yaml');
const _ = require("lodash");
const fs = require("fs");
const pluginName = 'generatePartialsPlugin';
const YAML = require('yaml')
const kineticLibraries = require('./kineticLibraries');
class kineticLibrariesPartialsPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
pluginName,
(compilation) => {
compilation.hooks.afterOptimizeChunkIds.tap(pluginName, () => {
const libraries = {};
for (const chunk of compilation.chunks) {
// If the chunk has a filenameTemplate, it is a split module.
if (chunk.filenameTemplate) {
// Generate the library definition.
let library = this.generateLibraryFromChunk(chunk);
let libraryName = Object.keys(library)[0];
libraries[libraryName] = _.merge(libraries[libraryName], library[libraryName]);
if (chunk.runtime instanceof Set) {
chunk.runtime.forEach(runtimePath => {
let library = kineticLibraries.getLibraryFromEntryPoint(runtimePath);
library.addDependency('kinetic/' + libraryName);
})
}
// todo: confirm if this will always be string for single item.
else {
let library = kineticLibraries.getLibraryFromEntryPoint(chunk.runtime );
library.addDependency('kinetic/' + libraryName);
}
}
}
// Create the partials.yml file for all split chunks.
this.createLibrariesYaml(libraries);
// For each kineticLibraries with dependencies attachPartialDependencies
for (const kineticLibraryName in kineticLibraries.getLibraries()) {
this.attachPartialDependencies(kineticLibraries.getLibraries()[kineticLibraryName]);
};
});
},
);
}
/**
* Get the chunk name.
*
* todo: find a better way to do this.
*
* @param chunk
* @returns {*}
*/
getChunkFilePath(chunk) {
const name = chunk.name || chunk.id;
if (typeof chunk.filenameTemplate === 'function') {
return chunk.filenameTemplate(name);
}
return chunk.filenameTemplate.replace(/\[name\]/g, name);
}
/**
* Generate the library definition from a chunk.
*
* @param chunk
* @returns {{}}
*/
generateLibraryFromChunk(chunk) {
const definition = {};
const filePath = this.getChunkFilePath(chunk);
const pathArr = filePath.split('/');
const fileName = pathArr.slice(-1)[0];
const entry = 'partials.' + fileName.replace(/^\d+-/ig, '');
definition[entry] = {};
let ext = fileName.split('.').pop();
// todo: come back to css support.
if (ext === 'js') {
_.set(definition[entry], `js['${filePath}']`, {'preprocess': false, minified: true});
}
return definition;
}
/**
* Create the partials.yml file.
*/
createLibrariesYaml(libraries) {
// Write out file.
write_yaml('partials.yml', libraries, (err) => {
console.log('Generating partials.yml');
if (err) {
console.error('ERROR: Could not generate partials.yml');
}
});
}
/**
* Create the componentDependencies.yml file.
*
* @param {object} ComponentDependencies
*/
attachPartialDependencies(library) {
try {
const file = fs.readFileSync(library.libraryPath, 'utf8')
let Metadata = YAML.parse(file)
let dependencies = _.get(Metadata, library.getDependencyIndex(), []) ?? [];
let updatedDependencies = dependencies;
if (dependencies.length !== 0) {
console.log(`Checking dependencies in ${library.libraryPath}`);
updatedDependencies = updatedDependencies.filter((item) => {
return !item.startsWith('kinetic/partials.');
});
}
updatedDependencies = updatedDependencies.concat(library.getDependencies());
if (!_.isEqual(dependencies, updatedDependencies)) {
_.set(Metadata, library.getDependencyIndex(), updatedDependencies);
write_yaml(library.libraryPath, Metadata, (err) => {
console.log(`Successfully added partials dependencies for ${library.libraryPath}`);
if (err) {
console.error(`ERROR: Could not add partials dependencies for ${library.libraryPath}`);
}
});
}
} catch(err) {
console.error(err);
}
}
}
module.exports = kineticLibrariesPartialsPlugin;
This diff is collapsed.
/*!
* Bootstrap backdrop.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap base-component.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap component-functions.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap config.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap data.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap event-handler.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap focustrap.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap index.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap manipulator.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap modal.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap scrollbar.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Bootstrap selector-engine.js v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
......@@ -8,11 +8,32 @@
// *********************************************
// 2. Theme suggestions
// *********************************************
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Asset\Exception\InvalidLibraryFileException;
use Drupal\Core\Serialization\Yaml;
function kinetic_library_info_alter(&$libraries, $extension) {
// Set all sdc libraries as minified.
if ($extension == 'sdc') {
foreach ($libraries as $name => $library) {
if (isset($library['css'])) {
foreach ($library['css'] as $type => $value) {
foreach ($library['css'][$type] as $key => $value) {
// $libraries[$name]['css'][$type][$key]['preprocess'] = false;
$libraries[$name]['css'][$type][$key]['minified'] = true;
}
}
}
if (isset($library['js'])) {
foreach ($library['js'] as $key => $value) {
// $libraries[$name]['js'][$key]['preprocess'] = false;
$libraries[$name]['js'][$key]['minified'] = true;
}
}
}
}
// Get the path of the theme where this function is being called.
$theme_path = \Drupal::service('extension.list.theme')->getPath('kinetic');;
// Alter only the library definitions of the current theme.
......
# Base Kinetic library
kinetic:
version: VERSION
header: true
js:
dist/js/index.js: {}
dist/js/index.js: {preprocess: false, minified: true}
css:
theme:
dist/css/index.css: {}
dist/css/utilities.css: {}
base:
dist/css/index.css: {preprocess: false, minified: true}
dist/css/utilities.css: {preprocess: false, minified: true}
dependencies:
- core/drupal
- sdc/kinetic--table
# Starterkit
base:
version: VERSION
css:
......@@ -83,12 +79,12 @@ image-widget:
version: VERSION
css:
component:
source/01-base/core/css/components/image-widget.css: { }
source/01-base/core/css/components/image-widget.css: {}
indented:
version: VERSION
css:
component:
source/01-base/core/css/components/indented.css: { }
source/01-base/core/css/components/indented.css: {}
messages:
version: VERSION
css:
......@@ -111,7 +107,7 @@ search-results:
version: VERSION
css:
component:
source/01-base/core/css/components/search-results.css: { }
source/01-base/core/css/components/search-results.css: {}
user:
version: VERSION
css:
......
......@@ -8,8 +8,8 @@
},
"main": "index.js",
"scripts": {
"build": "rm -rf dist; node build-utils/generatePartials.js; webpack --config webpack.config.js --mode=production",
"dev": "node build-utils/generatePartials.js; webpack --config webpack.config.js --watch --mode=development; drush cr",
"build": "rm -rf dist; webpack --config webpack.config.js --mode=production",
"dev": "webpack --config webpack.config.js --watch --mode=development; drush cr",
"eslint": "eslint",
"stylelint": "stylelint"
},
......@@ -71,7 +71,8 @@
"cross-env": "^7.0.3",
"normalize-scss": "^7.0.1",
"svg.js": "^2.6.5",
"what-input": "^5.2.10"
"what-input": "^5.2.10",
"yaml": "^2.3.1"
},
"extra": {
"patches": {
......
{}
partials.vendors.568.js:
js:
dist/js/vendors.568.js: {}
'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json'
name: "Modal"
status: "stable"
description: "A bootstrap modal. Providing a trigger and a dialog."
$schema: >-
https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json
name: Modal
status: stable
description: A bootstrap modal. Providing a trigger and a dialog.
props:
type: object
properties:
"test":
type: "string"
title: "Test"
description: "test prop"
test:
type: string
title: Test
description: test prop
libraryOverrides:
dependencies:
- kinetic/partials.vendors.568.js
This diff is collapsed.
......@@ -12,20 +12,9 @@ const ESLintPlugin = require('eslint-webpack-plugin');
const StylelintPlugin = require('stylelint-webpack-plugin');
const glob = require('glob');
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin');
// Require entry points.
const sdcEntries = require('./build-utils/generateSDCEntryPoints');
const { generatePartialsEntries, createPartialsYaml } = require('./build-utils/generatePartials');
const { staticEntries } = require('./kinetic.config');
const partialsEntries = generatePartialsEntries();
createPartialsYaml(partialsEntries);
const entryPoints = {
...staticEntries,
...partialsEntries,
...sdcEntries,
};
const kineticLibraries = require('./build-utils/kineticLibraries');
const kineticLibrariesPartialsPlugin = require('./build-utils/kineticLibrariesPartialsPlugin');
const entryPoints = kineticLibraries.getEntryPointsList();
const compiledEntries = {};
......@@ -43,6 +32,7 @@ module.exports = (env, argv) => {
}),
new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({ filename: '[name].css' }),
new kineticLibrariesPartialsPlugin(),
];
if (!isDev) {
......@@ -72,13 +62,39 @@ module.exports = (env, argv) => {
alias: {
helpers: path.resolve(__dirname, 'source/01-base/helpers'),
assets: path.resolve(__dirname, 'source/01-base/assets'),
components: path.resolve(__dirname, 'source/02-components'),
},
},
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/](?=.*\.(js|jsx|vue|json)$)/,
chunks: 'all',
minSize: 1000,
filename: 'dist/js/vendors.[name].js',
reuseExistingChunk: true,
priority: -5,
},
helpers: {
test: /[\\/]helpers[\\/](?=.*\.(js|jsx|vue|json)$)/,
chunks: 'all',
minSize: 1000,
filename: 'dist/js/helpers.[name].js',
reuseExistingChunk: true,
priority: -10,
},
components: {
test: /[\\/]02-components[\\/](?=.*\.(js|jsx|vue|json)$)/,
chunks: 'all',
minSize: 1000,
filename: 'dist/js/components.[name].js',
reuseExistingChunk: true,
priority: -20,
},
},
},
},
// cache: {
// type: 'filesystem',
// compression: 'gzip',
// },
cache: false,
......@@ -92,7 +108,7 @@ module.exports = (env, argv) => {
module: {
rules: [
{
test: /\.js$/,
test: /\.(js|jsx|vue|json)$/,
// Must add exceptions to this exclude statement for
// anything that needs to be transpiled by babel.
exclude: [/node_modules\/(?!bootstrap)/],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment