29
0
mirror of https://github.com/joomla/joomla-cms.git synced 2024-06-25 23:02:55 +00:00

[4.0] Build Tools code cleanup (#32300)

This commit is contained in:
Dimitris Grammatikogiannis 2021-02-18 10:36:43 +01:00 committed by GitHub
parent 399cf4c66c
commit 52fc4b86a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 3075 additions and 4403 deletions

View File

@ -1,9 +1,8 @@
const Fs = require('fs');
const FsExtra = require('fs-extra');
const Path = require('path');
const Recurs = require('recursive-readdir');
const UglyCss = require('uglifycss');
const CompileScss = require('./stylesheets/scss-transform.es6.js');
const { stat } = require('fs-extra');
const { sep } = require('path');
const recursive = require('recursive-readdir');
const { handleScssFile } = require('./stylesheets/handle-scss.es6.js');
const { handleCssFile } = require('./stylesheets/handle-css.es6.js');
const RootPath = process.cwd();
@ -20,96 +19,70 @@ const RootPath = process.cwd();
* @param {object} options The options
* @param {string} path The folder that needs to be compiled, optional
*/
module.exports.compile = (options, path) => {
Promise.resolve()
// Compile the scss files
.then(() => {
const files = [];
let folders = [];
module.exports.stylesheets = async (options, path) => {
const files = [];
let folders = [];
if (path) {
const stats = Fs.lstatSync(`${RootPath}/${path}`);
if (path) {
const stats = await stat(`${RootPath}/${path}`);
if (stats.isDirectory()) {
folders.push(`${RootPath}/${path}`);
} else if (stats.isFile()) {
files.push(`${RootPath}/${path}`);
} else {
// eslint-disable-next-line no-console
console.error(`Unknown path ${path}`);
process.exit(1);
}
} else {
folders = [
`${RootPath}/build/media_source`,
`${RootPath}/templates`,
`${RootPath}/installation/template`,
`${RootPath}/administrator/templates`,
];
if (stats.isDirectory()) {
folders.push(`${RootPath}/${path}`);
} else if (stats.isFile()) {
files.push(`${RootPath}/${path}`);
} else {
// eslint-disable-next-line no-console
console.error(`Unknown path ${path}`);
process.exit(1);
}
} else {
folders = [
`${RootPath}/build/media_source`,
`${RootPath}/templates`,
`${RootPath}/administrator/templates`,
`${RootPath}/installation/template`,
`${RootPath}/media/vendor/debugbar`,
];
}
const folderPromises = [];
// Loop to get the files that should be compiled via parameter
// eslint-disable-next-line no-restricted-syntax
for (const folder of folders) {
folderPromises.push(recursive(folder, ['!*.+(scss|css)']));
}
const computedFiles = await Promise.all(folderPromises);
const cssFilesPromises = [];
const scssFilesPromises = [];
// Loop to get the files that should be compiled via parameter
[].concat(...computedFiles).forEach((file) => {
if (file.endsWith('.css') && !file.endsWith('.min.css')) {
cssFilesPromises.push(handleCssFile(file));
}
// Don't take files with "_" but "file" has the full path, so check via match
if (file.endsWith('.scss') && !file.match(/(\/|\\)_[^/\\]+$/)) {
// Bail out for non Joomla convention folders, eg: scss
if (!(file.match(/\/scss\//) || file.match(/\\scss\\/))) {
return;
}
// Loop to get the files that should be compiled via parameter
folders.forEach((folder) => {
Recurs(folder, ['*.js', '*.map', '*.svg', '*.png', '*.gif', '*.swf', '*.html', '*.json']).then(
(filesRc) => {
filesRc.forEach(
(file) => {
// Don't take files with "_" but "file" has the full path, so check via match
if (file.match(/\.scss$/)) {
// Bail out for non Joomla convention folders, eg: scss
if (!(file.match(/\/scss\//) || file.match(/\\scss\\/))) {
return;
}
// Ignore files starting with _
if (!file.match(/(\/|\\)_[^/\\]+$/)) {
files.push(file);
}
files.push(file);
}
});
// Update the scss in the media folder
if (file.match(/build\/media_source\//)) {
FsExtra.mkdirsSync(Path.dirname(file).replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'), {});
Fs.copyFileSync(file, file.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'));
}
}
if (file.match(/\.css/) && !(file.match(/\/template(s)?\//) || file.match(/\\template(s)?\\/))) {
// CSS file, we will copy the file and then minify it in place
// Ensure that the directories exist or create them
FsExtra.mkdirsSync(Path.dirname(file).replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'), {});
Fs.copyFileSync(file, file.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'));
Fs.writeFileSync(
file.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.css', '.min.css'),
UglyCss.processFiles([file], { expandVars: false }),
{ encoding: 'utf8' },
);
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
const outputFile = file.replace(`${sep}scss${sep}`, `${sep}css${sep}`)
.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`)
.replace('.scss', '.css');
// eslint-disable-next-line no-console
console.log(`CSS file copied/minified: ${file}`);
}
},
(error) => {
// eslint-disable-next-line no-console
console.error(error.formatted);
},
);
scssFilesPromises.push(handleScssFile(file, outputFile));
}
return files;
},
).then(
(scssFiles) => {
scssFiles.forEach(
(inputFile) => {
CompileScss.compile(inputFile);
},
);
},
);
});
})
// Handle errors
.catch((error) => {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
});
await Promise.all([...cssFilesPromises, ...scssFilesPromises]);
};

View File

@ -1,7 +1,9 @@
const Fs = require('fs');
const { stat } = require('fs-extra');
const { sep } = require('path');
const Recurs = require('recursive-readdir');
const HandleFile = require('./javascript/handle-file.es6.js');
const recursive = require('recursive-readdir');
const { handleES5File } = require('./javascript/handle-es5.es6.js');
const { handleESMFile } = require('./javascript/compile-es6.es6.js');
const { handleWCFile } = require('./javascript/compile-w-c.es6.js');
const RootPath = process.cwd();
@ -19,50 +21,58 @@ const RootPath = process.cwd();
* @param { object } options The options from settings.json
* @param { string } path The folder that needs to be compiled, optional
*/
module.exports.compileJS = (options, path) => {
Promise.resolve(options, path)
// Compile the scss files
.then(() => {
let folders = [];
if (path) {
const stats = Fs.lstatSync(`${RootPath}/${path}`);
module.exports.scripts = async (options, path) => {
const files = [];
let folders = [];
if (!stats.isDirectory()) {
// @todo: allow to compile single file
throw new Error(`Path should be a directory: ${path}`);
}
if (path) {
const stats = await stat(`${RootPath}/${path}`);
folders.push(`${RootPath}/${path}`);
} else {
folders = [
`${RootPath}/build/media_source`,
`${RootPath}/templates/cassiopeia/js`,
];
}
if (stats.isDirectory()) {
folders.push(`${RootPath}/${path}`);
} else if (stats.isFile()) {
files.push(`${RootPath}/${path}`);
} else {
// eslint-disable-next-line no-console
console.error(`Unknown path ${path}`);
process.exit(1);
}
} else {
folders = [
`${RootPath}/build/media_source`,
`${RootPath}/templates/cassiopeia`,
];
}
// Loop to get some text for the packgage.json
folders.forEach((folder) => {
Recurs(folder, ['*.min.js', '*.map', '*.scss', '*.css', '*.svg', '*.png', '*.swf', '*.gif', '*.json']).then(
(files) => {
files.forEach(
(file) => {
if (file.includes(`build${sep}media_source${sep}vendor${sep}bootstrap${sep}js`)) {
return;
}
HandleFile.run(file);
},
(error) => {
// eslint-disable-next-line no-console
console.error(error.formatted);
},
);
},
);
});
})
const folderPromises = [];
// Handle errors
.catch((error) => {
throw new Error(`${error}`);
});
// Loop to get the files that should be compiled via parameter
// eslint-disable-next-line no-restricted-syntax
for (const folder of folders) {
folderPromises.push(recursive(folder, ['!*.+(js)']));
}
const computedFiles = await Promise.all(folderPromises);
const computedFilesFlat = [].concat(...computedFiles);
const jsFilesPromises = [];
const wcFilesPromises = [];
const esmFilesPromises = [];
// Loop to get the files that should be compiled via parameter
computedFilesFlat.forEach((file) => {
if (file.includes(`build${sep}media_source${sep}vendor${sep}bootstrap${sep}js`)) {
return;
}
if (file.match(/\.es5\.js$/)) {
jsFilesPromises.push(handleES5File(file));
} else if (file.match(/\.w-c\.es6\.js$/)) {
wcFilesPromises.push(handleWCFile(file));
} else if (file.match(/\.es6\.js$/)) {
esmFilesPromises.push(handleESMFile(file));
}
});
await Promise.all([...jsFilesPromises, ...wcFilesPromises, ...esmFilesPromises]);
};

View File

@ -0,0 +1,26 @@
const { getFiles } = require('@dgrammatiko/compress/src/getFiles.js');
const { compressFile } = require('@dgrammatiko/compress/src/compressFile.js');
/**
* Method that will pre compress (gzip) all .css/.js files
* in the templates and in the media folder
*/
module.exports.compressFiles = async (enableBrotli = false) => {
const paths = [
`${process.cwd()}/media`,
`${process.cwd()}/installation/template`,
`${process.cwd()}/templates`,
`${process.cwd()}/administrator/templates`,
];
const tasks = [];
const compressTasks = [];
paths.map((path) => tasks.push(getFiles(`${path}/`)));
const files = await Promise.all(tasks);
[].concat(...files).map((file) => compressTasks.push(compressFile(file, enableBrotli)));
await Promise.all(compressTasks);
// eslint-disable-next-line no-console
console.log('Done 👍');
};

View File

@ -1,20 +1,20 @@
const Fs = require('fs');
const {
access, mkdir, readFile, writeFile,
} = require('fs').promises;
const Ini = require('ini');
const Path = require('path');
const { dirname } = require('path');
const Recurs = require('recursive-readdir');
const UglifyCss = require('uglifycss');
const UglifyJs = require('uglify-es');
const Postcss = require('postcss');
const Autoprefixer = require('autoprefixer');
const CssNano = require('cssnano');
const { minify } = require('terser');
const RootPath = process.cwd();
const dir = `${RootPath}/installation/language`;
const srcPath = `${RootPath}/build/warning_page`;
// Set the initial template
let incomplete = 'window.errorLocale = {';
let unsupported = 'window.errorLocale = {';
/**
* Will produce 2 .html files
* Will produce as many .html files as defined in settings.json
* Expects three files:
* build/warning_page/template.css
* build/warning_page/template.html
@ -23,80 +23,141 @@ let unsupported = 'window.errorLocale = {';
* And also specific strings in the languages in the installation folder!
* Also the base strings are held in build/build-modules-js/settings.json
*/
module.exports.run = (options) => {
const initTemplate = Fs.readFileSync(`${srcPath}/template.html`, 'utf-8');
let cssContent = Fs.readFileSync(`${srcPath}/template.css`, 'utf-8');
let jsContent = Fs.readFileSync(`${srcPath}/template.js`, 'utf-8');
module.exports.createErrorPages = async (options) => {
const iniFilesProcess = [];
const processPages = [];
this.incompleteObj = {};
this.unsupportedObj = {};
this.fatalObj = {};
this.noxmlObj = {};
cssContent = UglifyCss.processString(cssContent, { expandVars: false });
jsContent = UglifyJs.minify(jsContent);
const initTemplate = await readFile(`${srcPath}/template.html`, { encoding: 'utf8' });
let cssContent = await readFile(`${srcPath}/template.css`, { encoding: 'utf8' });
let jsContent = await readFile(`${srcPath}/template.js`, { encoding: 'utf8' });
Recurs(dir).then(
(files) => {
files.sort().forEach((file) => {
const languageStrings = Ini.parse(Fs.readFileSync(file, 'UTF-8'));
const cssMin = await Postcss([Autoprefixer, CssNano]).process(cssContent, { from: undefined });
///
cssContent = cssMin.css;
jsContent = await minify(jsContent);
// Build the variables into json for the unsupported page
if (languageStrings.MIN_PHP_ERROR_LANGUAGE) {
const name = Path.dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
unsupported += `"${name}":{"language":"${languageStrings.MIN_PHP_ERROR_LANGUAGE}",`
+ `"header":"${languageStrings.MIN_PHP_ERROR_HEADER}",`
+ `"text1":"${languageStrings.MIN_PHP_ERROR_TEXT}",`
+ `"help-url-text":"${languageStrings.MIN_PHP_ERROR_URL_TEXT}"},`;
}
const processIni = async (file) => {
const languageStrings = Ini.parse(await readFile(file, { encoding: 'utf8' }));
// Build the variables into json for the unsupported page
if (languageStrings.BUILD_INCOMPLETE_LANGUAGE) {
const name = Path.dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
incomplete += `"${name}":{"language":"${languageStrings.BUILD_INCOMPLETE_LANGUAGE}",`
+ `"header":"${languageStrings.BUILD_INCOMPLETE_HEADER}",`
+ `"text1":"${languageStrings.BUILD_INCOMPLETE_TEXT}",`
+ `"help-url-text":"${languageStrings.BUILD_INCOMPLETE_URL_TEXT}"},`;
}
});
// Build the variables into json for the unsupported page
if (languageStrings.MIN_PHP_ERROR_LANGUAGE) {
const name = dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
this.unsupportedObj = {
...this.unsupportedObj,
[name]: {
language: languageStrings.MIN_PHP_ERROR_LANGUAGE,
header: languageStrings.MIN_PHP_ERROR_HEADER,
text1: languageStrings.MIN_PHP_ERROR_TEXT,
'help-url-text': languageStrings.MIN_PHP_ERROR_URL_TEXT,
},
};
}
unsupported = `${unsupported}}`;
incomplete = `${incomplete}}`;
// Build the variables into json for the build incomplete page
if (languageStrings.BUILD_INCOMPLETE_LANGUAGE) {
const name = dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
this.incompleteObj = {
...this.incompleteObj,
[name]: {
language: languageStrings.BUILD_INCOMPLETE_LANGUAGE,
header: languageStrings.BUILD_INCOMPLETE_HEADER,
text1: languageStrings.BUILD_INCOMPLETE_TEXT,
'help-url-text': languageStrings.BUILD_INCOMPLETE_URL_TEXT,
},
};
}
Object.keys(options.settings.errorPages).sort().forEach((name) => {
let checkContent = initTemplate;
checkContent = checkContent.replace('{{jsonContents}}', name === 'incomplete' ? incomplete : unsupported);
checkContent = checkContent.replace('{{PHP_VERSION}}', '');
checkContent = checkContent.replace('{{Title}}', options.settings.errorPages[name].title);
checkContent = checkContent.replace('{{Header}}', options.settings.errorPages[name].header);
checkContent = checkContent.replace('{{Description}}', options.settings.errorPages[name].text);
checkContent = checkContent.replace('{{Link}}', options.settings.errorPages[name].link);
checkContent = checkContent.replace('{{LinkText}}', options.settings.errorPages[name].linkText);
// Build the variables into json for the fatal error page
if (languageStrings.BUILD_FATAL_LANGUAGE) {
const name = dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
this.fatalObj = {
...this.fatalObj,
[name]: {
language: languageStrings.BUILD_FATAL_LANGUAGE,
header: languageStrings.BUILD_FATAL_HEADER,
text1: languageStrings.BUILD_FATAL_TEXT,
'help-url-text': languageStrings.BUILD_FATAL_URL_TEXT,
},
};
}
if (cssContent) {
checkContent = checkContent.replace('{{cssContents}}', cssContent);
}
// Build the variables into json for the missing XML error page
if (languageStrings.BUILD_NOXML_LANGUAGE) {
const name = dirname(file).replace(/.+\//, '').replace(/.+\\/, '');
this.noxmlObj = {
...this.noxmlObj,
[name]: {
language: languageStrings.BUILD_NOXML_LANGUAGE,
header: languageStrings.BUILD_NOXML_HEADER,
text1: languageStrings.BUILD_NOXML_TEXT,
'help-url-text': languageStrings.BUILD_NOXML_URL_TEXT,
},
};
}
};
if (jsContent) {
checkContent = checkContent.replace('{{jsContents}}', jsContent.code);
}
const files = await Recurs(dir);
files.sort().forEach((file) => {
iniFilesProcess.push(processIni(file));
});
Fs.writeFile(
`${RootPath}${options.settings.errorPages[name].destFile}`,
checkContent,
{ encoding: 'utf8' },
(err) => {
if (err) {
// eslint-disable-next-line no-console
console.log(err);
return;
}
await Promise.all(iniFilesProcess).catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(-1);
});
// eslint-disable-next-line no-console
console.log(`The ${options.settings.errorPages[name].destFile} page was created successfully!`);
},
);
});
},
(error) => {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
},
);
const processPage = async (name) => {
const jsonContent = `window.errorLocale=${JSON.stringify(this[`${name}Obj`])};`;
let template = initTemplate;
template = template.replace('{{jsonContents}}', jsonContent);
template = template.replace('{{Title}}', options.settings.errorPages[name].title);
template = template.replace('{{Header}}', options.settings.errorPages[name].header);
template = template.replace('{{Description}}', options.settings.errorPages[name].text);
template = template.replace('{{Link}}', options.settings.errorPages[name].link);
template = template.replace('{{LinkText}}', options.settings.errorPages[name].linkText);
if (cssContent) {
template = template.replace('{{cssContents}}', cssContent);
}
if (jsContent) {
template = template.replace('{{jsContents}}', jsContent.code);
}
let mediaExists = false;
try {
await access(dirname(`${RootPath}${options.settings.errorPages[name].destFile}`));
mediaExists = true;
} catch (err) {
// Do nothing
}
if (!mediaExists) {
await mkdir(dirname(`${RootPath}${options.settings.errorPages[name].destFile}`), { recursive: true });
}
await writeFile(
`${RootPath}${options.settings.errorPages[name].destFile}`,
template,
{ encoding: 'utf8' },
);
// eslint-disable-next-line no-console
console.error(`Created the file: ${options.settings.errorPages[name].destFile}`);
};
Object.keys(options.settings.errorPages).forEach((name) => processPages.push(processPage(name)));
await Promise.all(processPages).catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(-1);
});
};

View File

@ -1,131 +0,0 @@
/**
* For creating Brotli files you need to install iltorb
* and import it like:
* const { compressStream } = require('iltorb');
*/
const Fs = require('fs');
const { gzip } = require('@gfx/zopfli');
const walkSync = require('walk-sync');
const RootPath = process.cwd();
const compressStream = '';
const options = {
verbose: false,
verbose_more: false,
numiterations: 15,
blocksplitting: true,
blocksplittingmax: 15,
};
/**
* Method that will create a gzipped vestion of the given file
*
* @param { string } file The path of the file
*
* @returns { void }
*/
const handleFile = (file, enableBrotli) => {
if (file.match('/images') || file.match('\\images')) {
return;
}
if (file.match(/\.min\.js/) && !file.match(/\.min\.js\.gz/) && !file.match(/\.min\.js\.br/) && !file.toLowerCase().match(/json/) && !file.toLowerCase().match(/license/)) {
// eslint-disable-next-line no-console
console.log(`Processing: ${file}`);
if (enableBrotli && compressStream) {
// Brotli file
Fs.createReadStream(file)
.pipe(compressStream())
.pipe(Fs.createWriteStream(file.replace(/\.js$/, '.js.br')));
} else {
// Gzip the file
Fs.readFile(file, (err, data) => {
if (err) throw err;
gzip(data, options, (error, output) => {
if (error) throw err;
// Save the gzipped file
Fs.writeFileSync(
file.replace(/\.js$/, '.js.gz'),
output,
{ encoding: 'utf8' },
);
});
});
}
}
if (file.match(/\.min\.css/) && !file.match(/\.min\.css\.gz/) && !file.match(/\.min\.css\.br/) && !file.match(/\.css\.map/) && !file.toLowerCase().match(/license/)) {
// eslint-disable-next-line no-console
console.log(`Processing: ${file}`);
if (enableBrotli && compressStream) {
// Brotli file
Fs.createReadStream(file)
.pipe(compressStream())
.pipe(Fs.createWriteStream(file.replace(/\.css$/, '.css.br')));
} else {
// Gzip the file
Fs.readFile(file, (err, data) => {
if (err) throw err;
gzip(data, options, (error, output) => {
if (error) throw err;
// Save the gzipped file
Fs.writeFileSync(
file.replace(/\.css$/, '.css.gz'),
output,
{ encoding: 'utf8' },
);
});
});
}
}
};
/**
* Method to gzip the script and stylesheet files
*
* @param brotliParam {string} The CLI argument
*
* @returns { void }
*/
const gzipFiles = (brotliParam) => {
let enableBrotli = false;
if (brotliParam === 'brotli') {
enableBrotli = true;
}
// Minify the legacy files
// eslint-disable-next-line no-console
console.log('Gziping stylesheets and scripts...');
const templatesFiles = walkSync(`${RootPath}/templates`, {
globs: ['**/*.{js,css}'],
includeBasePath: true,
ignore: [],
directories: false,
});
const adminTemplatesFiles = walkSync(`${RootPath}/administrator/templates`, {
globs: ['**/*.{js,css}'],
includeBasePath: true,
ignore: [],
directories: false,
});
const mediaFiles = walkSync(`${RootPath}/media`, {
globs: ['**/*.{js,css}'],
includeBasePath: true,
ignore: [],
directories: false,
});
if (templatesFiles.length) {
templatesFiles.forEach((file) => handleFile(file, enableBrotli));
}
if (adminTemplatesFiles.length) {
adminTemplatesFiles.forEach((file) => handleFile(file, enableBrotli));
}
if (mediaFiles.length) {
mediaFiles.forEach((file) => handleFile(file, enableBrotli));
}
};
module.exports.gzipFiles = gzipFiles;

View File

@ -1,363 +0,0 @@
const Copydir = require('copy-dir');
const Fs = require('fs');
const FsExtra = require('fs-extra');
const Path = require('path');
const RootPath = process.cwd();
const xmlVersionStr = /(<version>)(.+)(<\/version>)/;
/**
* Method that will erase the media/vendor folder
* and populate the debugbar assets
*
* @returns { void }
*/
const cleanVendors = () => {
if (Fs.existsSync(Path.join(RootPath, 'libraries/vendor/maximebf/debugbar/src/DebugBar/Resources'))) {
// Remove the vendor folder
FsExtra.removeSync(Path.join(RootPath, 'media/vendor'));
// eslint-disable-next-line no-console
console.error('/media/vendor has been removed.');
// Copy some assets from a PHP package
FsExtra.copySync(Path.join(RootPath, 'libraries/vendor/maximebf/debugbar/src/DebugBar/Resources'), Path.join(RootPath, 'media/vendor/debugbar'));
// Do some cleanup
FsExtra.removeSync(Path.join(RootPath, 'media/vendor/debugbar/vendor/font-awesome'));
FsExtra.removeSync(Path.join(RootPath, 'media/vendor/debugbar/vendor/jquery'));
} else {
// eslint-disable-next-line no-console
console.error('You need to run `npm install` AFTER the command `composer install`!!!. The debug plugin HASN\'T installed all its front end assets');
process.exit(1);
}
};
/**
* Copies all the files from a directory
*
* @param {string} dirName the name of the source folder
* @param {string} name the name of the destination folder
* @param {string} type the type of the folder, eg: js, css, fonts, images
*
* @returns { void }
*/
const copyAll = (dirName, name, type) => {
const folderName = dirName === '/' ? '/' : `/${dirName}`;
FsExtra.copySync(Path.join(RootPath, `node_modules/${name}/${folderName}`),
Path.join(RootPath, `media/vendor/${name.replace(/.+\//, '')}/${type}`));
};
/**
* Copies an array of files from a directory
*
* @param {string} dirName the name of the source folder
* @param {array} files the array of files to be be copied
* @param {string} name the name of the destination folder
* @param {string} type the type of the folder, eg: js, css, fonts, images
*
* @returns { void }
*/
const copyArrayFiles = (dirName, files, name, type) => {
files.forEach((file) => {
const folderName = dirName === '/' ? '/' : `/${dirName}/`;
if (FsExtra.existsSync(`node_modules/${name}${folderName}${file}`)) {
FsExtra.copySync(`node_modules/${name}${folderName}${file}`, `media/vendor/${name.replace(/.+\//, '')}${type ? `/${type}` : ''}/${file}`);
}
});
};
/**
*
* @param {object} files the object of files map, eg {"src.js": "js/src.js"}
* @param {string} srcDir the name of the package root dir
* @param {string} destDir the name of the Vendor destination dir
*
* @returns {Array}
*/
const copyFilesTo = (files, srcDir, destDir) => {
const filesResult = [];
// Copy each file
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const srcFile in files) {
const destFile = files[srcFile];
const srcPath = Path.join(srcDir, srcFile);
const destPath = Path.join(destDir, destFile);
FsExtra.copySync(srcPath, destPath);
filesResult.push(destPath);
}
return filesResult;
};
/**
* Method to concatenate some files
*
* @param {array} files the array of files to be be concatenated
* @param {string} output the name of the output file
*
* @returns {void}
*/
const concatFiles = (files, output) => {
let tempMem = '';
files.forEach((file) => {
if (FsExtra.existsSync(`${RootPath}/${file}`)) {
tempMem += Fs.readFileSync(`${RootPath}/${file}`);
}
});
Fs.writeFileSync(`${RootPath}/${output}`, tempMem);
};
/**
* Main method that will copy all vendor files according to Joomla's specs
*
* @param options The options from setting.json
*
* @returns {void}
*/
const copyFiles = (options) => {
const mediaVendorPath = Path.join(RootPath, 'media/vendor');
const registry = {
$schema: 'https://developer.joomla.org/schemas/json-schema/web_assets.json',
name: options.name,
version: options.version,
description: options.description,
license: options.license,
assets: [],
};
if (!FsExtra.existsSync(mediaVendorPath)) {
FsExtra.mkdirSync(mediaVendorPath);
}
// Loop to get some text for the packgage.json
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const packageName in options.settings.vendors) {
const vendor = options.settings.vendors[packageName];
const vendorName = vendor.name || packageName;
const modulePathJson = require.resolve(`${packageName}/package.json`);
const modulePathRoot = Path.dirname(modulePathJson);
// eslint-disable-next-line global-require, import/no-dynamic-require
const moduleOptions = require(modulePathJson);
if (packageName === 'codemirror') {
const itemvendorPath = Path.join(RootPath, `media/vendor/${packageName}`);
if (!FsExtra.existsSync(itemvendorPath)) {
FsExtra.mkdirSync(itemvendorPath);
FsExtra.mkdirSync(Path.join(itemvendorPath, 'addon'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'lib'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'mode'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'keymap'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'theme'));
}
copyAll('addon', 'codemirror', 'addon');
copyAll('lib', 'codemirror', 'lib');
copyAll('mode', 'codemirror', 'mode');
copyAll('keymap', 'codemirror', 'keymap');
copyAll('theme', 'codemirror', 'theme');
concatFiles(
[
'media/vendor/codemirror/addon/display/autorefresh.js',
'media/vendor/codemirror/addon/display/fullscreen.js',
'media/vendor/codemirror/addon/display/panel.js',
'media/vendor/codemirror/addon/edit/closebrackets.js',
'media/vendor/codemirror/addon/edit/closetag.js',
'media/vendor/codemirror/addon/edit/matchbrackets.js',
'media/vendor/codemirror/addon/edit/matchtags.js',
'media/vendor/codemirror/addon/fold/brace-fold.js',
'media/vendor/codemirror/addon/fold/foldcode.js',
'media/vendor/codemirror/addon/fold/foldgutter.js',
'media/vendor/codemirror/addon/fold/xml-fold.js',
'media/vendor/codemirror/addon/mode/loadmode.js',
'media/vendor/codemirror/addon/mode/multiplex.js',
'media/vendor/codemirror/addon/scroll/annotatescrollbar.js',
'media/vendor/codemirror/addon/scroll/simplescrollbars.js',
'media/vendor/codemirror/addon/scroll/matchesonscrollbar.js',
'media/vendor/codemirror/addon/scroll/match-highlighter.js',
'media/vendor/codemirror/addon/scroll/searchcursor.js',
'media/vendor/codemirror/addon/selection/active-line.js',
'media/vendor/codemirror/mode/meta.js',
],
'media/vendor/codemirror/lib/addons.js',
);
concatFiles(
[
'media/vendor/codemirror/lib/codemirror.js',
'media/vendor/codemirror/lib/addons.js',
],
'media/vendor/codemirror/lib/codemirror-ce.js',
);
concatFiles([
'media/vendor/codemirror/addon/display/fullscreen.css',
'media/vendor/codemirror/addon/fold/foldgutter.css',
'media/vendor/codemirror/addon/search/matchesonscrollbar.css',
'media/vendor/codemirror/addon/scroll/simplescrollbars.css',
], 'media/vendor/codemirror/lib/addons.css');
// Update the XML file for Codemirror
let codemirrorXml = Fs.readFileSync(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, { encoding: 'UTF-8' });
codemirrorXml = codemirrorXml.replace(xmlVersionStr, `$1${moduleOptions.version}$3`);
Fs.writeFileSync(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, codemirrorXml, { encoding: 'UTF-8' });
} else if (packageName === 'tinymce') {
const itemvendorPath = Path.join(RootPath, `media/vendor/${packageName}`);
if (!FsExtra.existsSync(itemvendorPath)) {
FsExtra.mkdirSync(itemvendorPath);
FsExtra.mkdirSync(Path.join(itemvendorPath, 'icons'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'plugins'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'langs'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'skins'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'themes'));
FsExtra.mkdirSync(Path.join(itemvendorPath, 'templates'));
}
copyAll('icons', 'tinymce', 'icons');
copyAll('plugins', 'tinymce', 'plugins');
copyAll('skins', 'tinymce', 'skins');
copyAll('themes', 'tinymce', 'themes');
copyArrayFiles('', ['tinymce.js', 'tinymce.min.js', 'changelog.txt', 'license.txt'], 'tinymce', '');
// Update the XML file for tinyMCE
let tinyXml = Fs.readFileSync(`${RootPath}/plugins/editors/tinymce/tinymce.xml`, { encoding: 'UTF-8' });
tinyXml = tinyXml.replace(xmlVersionStr, `$1${moduleOptions.version}$3`);
Fs.writeFileSync(`${RootPath}/plugins/editors/tinymce/tinymce.xml`, tinyXml, { encoding: 'UTF-8' });
// Remove that sourcemap...
let tinyWrongMap = Fs.readFileSync(`${RootPath}/media/vendor/tinymce/skins/ui/oxide/skin.min.css`, { encoding: 'UTF-8' });
tinyWrongMap = tinyWrongMap.replace('/*# sourceMappingURL=skin.min.css.map */', '');
Fs.writeFileSync(`${RootPath}/media/vendor/tinymce/skins/ui/oxide/skin.min.css`, tinyWrongMap, { encoding: 'UTF-8' });
} else {
['js', 'css', 'filesExtra'].forEach((type) => {
if (!vendor[type]) return;
const dest = Path.join(mediaVendorPath, vendorName);
copyFilesTo(vendor[type], modulePathRoot, dest, type);
});
// Copy the license if exists
if (options.settings.vendors[packageName].licenseFilename
&& Fs.existsSync(`${Path.join(RootPath, `node_modules/${packageName}`)}/${options.settings.vendors[packageName].licenseFilename}`)
) {
const dest = Path.join(mediaVendorPath, vendorName);
FsExtra.copySync(`${Path.join(RootPath, `node_modules/${packageName}`)}/${options.settings.vendors[packageName].licenseFilename}`, `${dest}/${options.settings.vendors[packageName].licenseFilename}`);
}
}
// Joomla's hack to expose the chosen base classes so we can extend it ourselves
// (it was better than the many hacks we had before. But I'm still ashamed of myself).
if (packageName === 'chosen-js') {
const dest = Path.join(mediaVendorPath, vendorName);
const chosenPath = `${dest}/${options.settings.vendors[packageName].js['chosen.jquery.js']}`;
let ChosenJs = Fs.readFileSync(chosenPath, { encoding: 'UTF-8' });
ChosenJs = ChosenJs.replace('}).call(this);', ' document.AbstractChosen = AbstractChosen;\n'
+ ' document.Chosen = Chosen;\n'
+ '}).call(this);');
Fs.writeFileSync(chosenPath, ChosenJs, { encoding: 'UTF-8' });
}
// Append initialising code to the end of the Short-and-Sweet javascript
if (packageName === 'short-and-sweet') {
const dest = Path.join(mediaVendorPath, vendorName);
const shortandsweetPath = `${dest}/${options.settings.vendors[packageName].js['dist/short-and-sweet.min.js']}`;
let ShortandsweetJs = Fs.readFileSync(shortandsweetPath, { encoding: 'UTF-8' });
ShortandsweetJs = ShortandsweetJs.concat('document.addEventListener(\'DOMContentLoaded\', function()'
+ '{shortAndSweet(\'textarea.charcount\', {counterClassName: \'small text-muted\'}); });');
Fs.writeFileSync(shortandsweetPath, ShortandsweetJs, { encoding: 'UTF-8' });
}
// Add provided Assets to a registry, if any
if (vendor.provideAssets && vendor.provideAssets.length) {
vendor.provideAssets.forEach((assetInfo) => {
const registryItemBase = {
package: packageName,
name: assetInfo.name || vendorName,
version: moduleOptions.version,
type: assetInfo.type,
};
const registryItem = Object.assign(assetInfo, registryItemBase);
// Update path to file
if (assetInfo.uri && (assetInfo.type === 'script' || assetInfo.type === 'style' || assetInfo.type === 'webcomponent')) {
let itemPath = assetInfo.uri;
// Check for external path
if (itemPath.indexOf('http://') !== 0 && itemPath.indexOf('https://') !== 0 && itemPath.indexOf('//') !== 0) {
itemPath = `vendor/${vendorName}/${itemPath}`;
}
registryItem.uri = itemPath;
}
registry.assets.push(registryItem);
});
}
// eslint-disable-next-line no-console
console.log(`${packageName} was updated.`);
}
// Write assets registry
Fs.writeFileSync(
Path.join(mediaVendorPath, 'joomla.asset.json'),
JSON.stringify(registry, null, 2),
{ encoding: 'UTF-8' },
);
// Restore our code on the vendor folders
FsExtra.copySync(Path.join(RootPath, 'build/media_source/vendor/tinymce/templates'), Path.join(RootPath, 'media/vendor/tinymce/templates'));
};
/**
* Method to recreate the basic media folder structure
* After execution the media folder is populated with empty js and css subdirectories
* images subfolders with the relative files
* any other files except .js, .css, .scss
*
* @returns { void }
*/
const recreateMediaFolder = () => {
// eslint-disable-next-line no-console
console.log('Recreating the media folder...');
Copydir.sync(Path.join(RootPath, 'build/media_source'), Path.join(RootPath, 'media'), (stat, filepath, filename) => {
if (stat === 'file' && filename.match(/\.(es6\.js|es5\.js|scss)$/)) {
return false;
}
if (stat === 'directory' && filename.match(/^(core\.es6|scss)$/)) {
return false;
}
return true;
}, (err) => {
if (!err) {
// eslint-disable-next-line no-console
console.log('Legacy media files restored');
}
});
};
module.exports.copyAssets = (options) => {
Promise.resolve()
// Copy a fresh version of the files
.then(cleanVendors())
// Copy a fresh version of the files
.then(recreateMediaFolder())
// Copy a fresh version of the files
.then(copyFiles(options))
// Handle errors
.catch((error) => {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
});
};

View File

@ -0,0 +1,38 @@
const {
stat, mkdir, copy, remove,
} = require('fs-extra');
const { join } = require('path');
const RootPath = process.cwd();
/**
* Method that will erase the media/vendor folder
* and populate the debugbar assets
*
* @returns {Promise}
*/
module.exports.cleanVendors = async () => {
// eslint-disable-next-line no-console
console.log('Cleanup the Vendor ');
const mediaFolder = await stat(join(RootPath, 'libraries/vendor/maximebf/debugbar/src/DebugBar/Resources'));
if (await mediaFolder.isDirectory()) {
// Remove the vendor folder
// await remove(join(RootPath, 'media'));
// eslint-disable-next-line no-console
// console.error('/media has been removed.');
// Recreate the media folder
await mkdir(join(RootPath, 'media/vendor/debugbar'), { recursive: true });
// Copy some assets from a PHP package
await copy(join(RootPath, 'libraries/vendor/maximebf/debugbar/src/DebugBar/Resources'), join(RootPath, 'media/vendor/debugbar'));
await remove(join(RootPath, 'media/vendor/debugbar/vendor/font-awesome'));
await remove(join(RootPath, 'media/vendor/debugbar/vendor/jquery'));
} else {
// eslint-disable-next-line no-console
console.error('You need to run `npm install` AFTER the command `composer install`!!!. The debug plugin HASN\'T installed all its front end assets');
process.exit(1);
}
};

View File

@ -0,0 +1,26 @@
const { readFile, writeFile, existsSync } = require('fs-extra');
const RootPath = process.cwd();
/**
* Method to concatenate some files
*
* @param {array} files the array of files to be be concatenated
* @param {string} output the name of the output file
*
* @returns {void}
*/
module.exports.concatFiles = async (files, output) => {
const promises = [];
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
if (existsSync(`${RootPath}/${file}`)) {
promises.push(readFile(`${RootPath}/${file}`, { encoding: 'utf8' }));
}
}
const res = await Promise.all(promises);
await writeFile(`${RootPath}/${output}`, res.join(' '));
};

View File

@ -0,0 +1,19 @@
const { copy } = require('fs-extra');
const { join } = require('path');
const RootPath = process.cwd();
/**
* Copies all the files from a directory
*
* @param {string} dirName the name of the source folder
* @param {string} name the name of the destination folder
* @param {string} type the type of the folder, eg: js, css, fonts, images
*
* @returns { void }
*/
module.exports.copyAllFiles = async (dirName, name, type) => {
const folderName = dirName === '/' ? '/' : `/${dirName}`;
await copy(join(RootPath, `node_modules/${name}/${folderName}`),
join(RootPath, `media/vendor/${name.replace(/.+\//, '')}/${type}`));
};

View File

@ -0,0 +1,78 @@
const {
existsSync, readFile, writeFile, mkdir,
} = require('fs-extra');
const { join } = require('path');
const { concatFiles } = require('../common/concat-files.es6.js');
const { copyAllFiles } = require('../common/copy-all-files.es6.js');
const RootPath = process.cwd();
const xmlVersionStr = /(<version>)(.+)(<\/version>)/;
/**
* Codemirror needs special treatment
*/
module.exports.codeMirror = async (packageName, version) => {
const itemvendorPath = join(RootPath, `media/vendor/${packageName}`);
if (!await existsSync(itemvendorPath)) {
await mkdir(itemvendorPath, { recursive: true });
await mkdir(join(itemvendorPath, 'addon'));
await mkdir(join(itemvendorPath, 'lib'));
await mkdir(join(itemvendorPath, 'mode'));
await mkdir(join(itemvendorPath, 'keymap'));
await mkdir(join(itemvendorPath, 'theme'));
}
await copyAllFiles('addon', 'codemirror', 'addon');
await copyAllFiles('lib', 'codemirror', 'lib');
await copyAllFiles('mode', 'codemirror', 'mode');
await copyAllFiles('keymap', 'codemirror', 'keymap');
await copyAllFiles('theme', 'codemirror', 'theme');
await concatFiles(
[
'media/vendor/codemirror/addon/display/fullscreen.js',
'media/vendor/codemirror/addon/display/panel.js',
'media/vendor/codemirror/addon/edit/closebrackets.js',
'media/vendor/codemirror/addon/edit/closetag.js',
'media/vendor/codemirror/addon/edit/matchbrackets.js',
'media/vendor/codemirror/addon/edit/matchtags.js',
'media/vendor/codemirror/addon/fold/brace-fold.js',
'media/vendor/codemirror/addon/fold/foldcode.js',
'media/vendor/codemirror/addon/fold/foldgutter.js',
'media/vendor/codemirror/addon/fold/xml-fold.js',
'media/vendor/codemirror/addon/mode/loadmode.js',
'media/vendor/codemirror/addon/mode/multiplex.js',
'media/vendor/codemirror/addon/scroll/annotatescrollbar.js',
'media/vendor/codemirror/addon/scroll/simplescrollbars.js',
'media/vendor/codemirror/addon/scroll/matchesonscrollbar.js',
'media/vendor/codemirror/addon/scroll/match-highlighter.js',
'media/vendor/codemirror/addon/scroll/searchcursor.js',
'media/vendor/codemirror/addon/selection/active-line.js',
'media/vendor/codemirror/mode/meta.js',
],
'media/vendor/codemirror/lib/addons.js',
);
await concatFiles(
[
'media/vendor/codemirror/lib/codemirror.js',
'media/vendor/codemirror/lib/addons.js',
],
'media/vendor/codemirror/lib/codemirror-ce.js',
);
await concatFiles([
'media/vendor/codemirror/addon/display/fullscreen.css',
'media/vendor/codemirror/addon/fold/foldgutter.css',
'media/vendor/codemirror/addon/search/matchesonscrollbar.css',
'media/vendor/codemirror/addon/scroll/simplescrollbars.css',
],
'media/vendor/codemirror/lib/addons.css');
// Update the XML file for Codemirror
let codemirrorXml = await readFile(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, { encoding: 'utf8' });
codemirrorXml = codemirrorXml.replace(xmlVersionStr, `$1${version}$3`);
await writeFile(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, codemirrorXml, { encoding: 'utf8' });
};

View File

@ -0,0 +1,70 @@
const {
existsSync, copy, readFile, writeFile, mkdir,
} = require('fs-extra');
const { join } = require('path');
const { copyAllFiles } = require('../common/copy-all-files.es6.js');
const RootPath = process.cwd();
const xmlVersionStr = /(<version>)(.+)(<\/version>)/;
/**
* Copies an array of files from a directory
*
* @param {string} dirName the name of the source folder
* @param {array} files the array of files to be be copied
* @param {string} name the name of the destination folder
* @param {string} type the type of the folder, eg: js, css, fonts, images
*
* @returns { void }
*/
const copyArrayFiles = async (dirName, files, name, type) => {
const promises = [];
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const file of files) {
const folderName = dirName === '/' ? '/' : `/${dirName}/`;
if (existsSync(`node_modules/${name}${folderName}${file}`)) {
promises.push(copy(`node_modules/${name}${folderName}${file}`, `media/vendor/${name.replace(/.+\//, '')}${type ? `/${type}` : ''}/${file}`));
}
}
await Promise.all(promises);
};
/**
* tinyMCE needs special treatment
*/
module.exports.tinyMCE = async (packageName, version) => {
const itemvendorPath = join(RootPath, `media/vendor/${packageName}`);
if (!await existsSync(itemvendorPath)) {
await mkdir(itemvendorPath);
await mkdir(join(itemvendorPath, 'icons'));
await mkdir(join(itemvendorPath, 'plugins'));
await mkdir(join(itemvendorPath, 'langs'));
await mkdir(join(itemvendorPath, 'skins'));
await mkdir(join(itemvendorPath, 'themes'));
await mkdir(join(itemvendorPath, 'templates'));
}
await copyAllFiles('icons', 'tinymce', 'icons');
await copyAllFiles('plugins', 'tinymce', 'plugins');
await copyAllFiles('skins', 'tinymce', 'skins');
await copyAllFiles('themes', 'tinymce', 'themes');
await copyArrayFiles('', ['tinymce.js', 'tinymce.min.js', 'changelog.txt', 'license.txt'], 'tinymce', '');
// Update the XML file for tinyMCE
let tinyXml = await readFile(`${RootPath}/plugins/editors/tinymce/tinymce.xml`, { encoding: 'utf8' });
tinyXml = tinyXml.replace(xmlVersionStr, `$1${version}$3`);
await writeFile(`${RootPath}/plugins/editors/tinymce/tinymce.xml`, tinyXml, { encoding: 'utf8' });
// Remove that sourcemap...
let tinyWrongMap = await readFile(`${RootPath}/media/vendor/tinymce/skins/ui/oxide/skin.min.css`, { encoding: 'utf8' });
tinyWrongMap = tinyWrongMap.replace('/*# sourceMappingURL=skin.min.css.map */', '');
await writeFile(`${RootPath}/media/vendor/tinymce/skins/ui/oxide/skin.min.css`, tinyWrongMap, { encoding: 'utf8' });
// Restore our code on the vendor folders
await copy(join(RootPath, 'build/media_source/vendor/tinymce/templates'), join(RootPath, 'media/vendor/tinymce/templates'));
};

View File

@ -0,0 +1,149 @@
const {
existsSync, copy, writeFile, mkdir, mkdirs, ensureDir,
} = require('fs-extra');
const { dirname, join } = require('path');
const { codeMirror } = require('./exemptions/codemirror.es6.js');
const { tinyMCE } = require('./exemptions/tinymce.es6.js');
const RootPath = process.cwd();
/**
*
* @param {object} files the object of files map, eg {"src.js": "js/src.js"}
* @param {string} srcDir the name of the package root dir
* @param {string} destDir the name of the Vendor destination dir
*
* @returns {Promise}
*/
const copyFilesTo = async (files, srcDir, destDir) => {
const copyPromises = [];
async function doTheCopy(source, dest) {
await ensureDir(dirname(dest));
await copy(source, dest);
}
// Copy each file
// eslint-disable-next-line no-restricted-syntax,guard-for-in
for (const srcFile in files) {
copyPromises.push(doTheCopy(join(srcDir, srcFile), join(destDir, files[srcFile])));
}
return Promise.all(copyPromises);
};
/**
* Main method that will resolve each vendor package
*
* @returns {Promise}
*/
const resolvePackage = async (vendor, packageName, mediaVendorPath, options, registry) => {
const vendorName = vendor.name || packageName;
const modulePathJson = require.resolve(`${packageName}/package.json`);
const modulePathRoot = dirname(modulePathJson);
// eslint-disable-next-line global-require, import/no-dynamic-require
const moduleOptions = require(modulePathJson);
const promises = [];
if (packageName === 'codemirror') {
promises.push(codeMirror(packageName, moduleOptions.version));
} else if (packageName === 'tinymce') {
promises.push(tinyMCE(packageName, moduleOptions.version));
} else {
await mkdirs(join(mediaVendorPath, vendorName));
['js', 'css', 'filesExtra'].forEach((type) => {
if (!vendor[type]) return;
promises.push(
copyFilesTo(vendor[type], modulePathRoot, join(mediaVendorPath, vendorName), type),
);
});
}
// Copy the license if existsSync
if (options.settings.vendors[packageName].licenseFilename
&& await existsSync(`${join(RootPath, `node_modules/${packageName}`)}/${options.settings.vendors[packageName].licenseFilename}`)
) {
const dest = join(mediaVendorPath, vendorName);
await copy(
`${join(RootPath, `node_modules/${packageName}`)}/${options.settings.vendors[packageName].licenseFilename}`,
`${dest}/${options.settings.vendors[packageName].licenseFilename}`,
);
}
await Promise.all(promises);
// Add provided Assets to a registry, if any
if (vendor.provideAssets && vendor.provideAssets.length) {
vendor.provideAssets.forEach((assetInfo) => {
const registryItemBase = {
package: packageName,
name: assetInfo.name || vendorName,
version: moduleOptions.version,
type: assetInfo.type,
};
const registryItem = Object.assign(assetInfo, registryItemBase);
// Update path to file
if (assetInfo.uri && (assetInfo.type === 'script' || assetInfo.type === 'style' || assetInfo.type === 'webcomponent')) {
let itemPath = assetInfo.uri;
// Check for external path
if (itemPath.indexOf('http://') !== 0 && itemPath.indexOf('https://') !== 0 && itemPath.indexOf('//') !== 0) {
itemPath = `vendor/${vendorName}/${itemPath}`;
}
registryItem.uri = itemPath;
}
registry.assets.push(registryItem);
});
}
// eslint-disable-next-line no-console
console.log(`${packageName} was updated.`);
};
/**
* Main method that will copy all vendor files according to Joomla's specs
*
* @param options The options from setting.json
*
* @returns {Promise}
*/
module.exports.localisePackages = async (options) => {
const mediaVendorPath = join(RootPath, 'media/vendor');
const registry = {
$schema: 'https://developer.joomla.org/schemas/json-schema/web_assets.json',
name: options.name,
version: options.version,
description: options.description,
license: options.license,
assets: [],
};
const promises = [];
if (!await existsSync(mediaVendorPath)) {
await mkdir(mediaVendorPath, { recursive: true });
}
// Loop to get some text for the packgage.json
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const packageName in options.settings.vendors) {
const vendor = options.settings.vendors[packageName];
promises.push(resolvePackage(vendor, packageName, mediaVendorPath, options, registry));
}
await Promise.all(promises);
// Write assets registry
await writeFile(
join(mediaVendorPath, 'joomla.asset.json'),
JSON.stringify(registry, null, 2),
{ encoding: 'utf8' },
);
};

View File

@ -0,0 +1,103 @@
const { lstat, readFile, writeFile } = require('fs-extra');
const { sep } = require('path');
const recursive = require('recursive-readdir');
const { minify } = require('terser');
const RootPath = process.cwd();
const folders = [
'media/vendor/accessibility/js',
'media/vendor/chosen/js',
'media/vendor/codemirror',
'media/vendor/debugbar',
'media/vendor/punycode/js',
'media/vendor/qrcode/js',
'media/vendor/short-and-sweet/js',
'media/vendor/webcomponentsjs/js',
];
let allFiles = [];
const noMinified = [
'media/vendor/accessibility/js/accessibility.min.js',
'media/vendor/short-and-sweet/js/short-and-sweet.min.js',
];
const alreadyMinified = [
'media/vendor/webcomponentsjs/js/webcomponents-ce.js',
'media/vendor/webcomponentsjs/js/webcomponents-sd.js',
'media/vendor/webcomponentsjs/js/webcomponents-sd-ce.js',
'media/vendor/webcomponentsjs/js/webcomponents-sd-ce-pf.js',
];
/**
* Check if a file exists
*
* @param file
* @returns {Promise<boolean>}
*/
const minifiedExists = async (file) => {
try {
return (await lstat(file)).isFile();
} catch (e) {
return false;
}
};
/**
*
* @param {string} file
*
* @returns {Promise}
*/
const minifyJS = async (file) => {
const needsDotJS = noMinified.includes(file.replace(`${RootPath}${sep}`, ''));
if (file.endsWith('.min.js') && !needsDotJS) {
return;
}
// eslint-disable-next-line no-console
console.log(`Processing Vendor file: ${file}`);
let minified;
const fileExists = await minifiedExists(file);
if (!fileExists) {
return;
}
const content = await readFile(file, { encoding: 'utf8' });
const isMinified = alreadyMinified.includes(file.replace(`${RootPath}${sep}`, ''));
if (isMinified || needsDotJS) {
minified = content;
} else {
minified = (await minify(content, { sourceMap: false, format: { comments: false } })).code;
}
const newFile = needsDotJS ? file.replace('.min.js', '.js') : file.replace('.js', '.min.js');
// Write the file
await writeFile(
newFile,
minified,
{ encoding: 'utf8' },
);
};
/**
* Method that will minify a set of vendor javascript files
*
* @returns {Promise}
*/
module.exports.minifyVendor = async () => {
// return;
const folderPromises = [];
const filesPromises = [];
folders.map((folder) => folderPromises.push(recursive(folder, ['!*.+(js)'])));
const computedFiles = await Promise.all(folderPromises);
allFiles = [...allFiles, ...[].concat(...computedFiles)];
allFiles.map((file) => filesPromises.push(minifyJS(file)));
return Promise.all(filesPromises);
};

View File

@ -0,0 +1,33 @@
const { readFile, writeFile } = require('fs-extra');
const { join } = require('path');
const RootPath = process.cwd();
/**
* Main method that will patch files...
*
* @param options The options from setting.json
*
* @returns {Promise}
*/
module.exports.patchPackages = async (options) => {
const mediaVendorPath = join(RootPath, 'media/vendor');
// Joomla's hack to expose the chosen base classes so we can extend it ourselves
// (it was better than the many hacks we had before. But I'm still ashamed of myself).
let dest = join(mediaVendorPath, 'chosen');
const chosenPath = `${dest}/${options.settings.vendors['chosen-js'].js['chosen.jquery.js']}`;
let ChosenJs = await readFile(chosenPath, { encoding: 'utf8' });
ChosenJs = ChosenJs.replace('}).call(this);', ` document.AbstractChosen = AbstractChosen;
document.Chosen = Chosen;
}).call(this);`);
await writeFile(chosenPath, ChosenJs, { encoding: 'utf8' });
// Append initialising code to the end of the Short-and-Sweet javascript
dest = join(mediaVendorPath, 'short-and-sweet');
const shortandsweetPath = `${dest}/${options.settings.vendors['short-and-sweet'].js['dist/short-and-sweet.min.js']}`;
let ShortandsweetJs = await readFile(shortandsweetPath, { encoding: 'utf8' });
ShortandsweetJs = ShortandsweetJs.concat('document.addEventListener(\'DOMContentLoaded\', function()'
+ '{shortAndSweet(\'textarea.charcount\', {counterClassName: \'small text-muted\'}); });');
await writeFile(shortandsweetPath, ShortandsweetJs, { encoding: 'utf8' });
};

View File

@ -0,0 +1,31 @@
const { stat, copy } = require('fs-extra');
const { join, extname } = require('path');
const RootPath = process.cwd();
/**
* Method to recreate the basic media folder structure
* After execution the media folder is populated with empty js and css subdirectories
* images subfolders with their relative files and any other files except .js, .css
*
* @returns {Promise}
*/
module.exports.recreateMediaFolder = async () => {
// eslint-disable-next-line no-console
console.log('Recreating the media folder...');
const filterFunc = async (src) => {
const fileStat = await stat(src);
if (fileStat.isDirectory() && src.endsWith('core.es6')) {
return false;
}
if (fileStat.isFile() && (extname(src) === '.js' || extname(src) === '.css')) {
return false;
}
return true;
};
await copy(join(RootPath, 'build/media_source'), join(RootPath, 'media'), { filter: filterFunc });
};

View File

@ -1,36 +1,53 @@
const { writeFile } = require('fs').promises;
const Babel = require('@babel/core');
const Fs = require('fs');
const FsExtra = require('fs-extra');
const Path = require('path');
const { dirname } = require('path');
const { minify } = require('terser');
/**
* Create the minified file as well
*
* @returns {Promise<void>}
* @param fileName {string}
* @param data {string}
*/
const createMinified = async (fileName, data) => {
const mini = await minify(data, { sourceMap: false, format: { comments: false } });
await writeFile(fileName, mini.code, { encoding: 'utf8' });
};
/**
*
* @param fileContents the content of the file to be transpiled
* @param settings the settings for the transpiler
* @param output the full pat + filename + extension of the trnspiled file
* @param output the full pat + filename + extension of the transpiled file
*/
module.exports.run = (fileContents, settings, output) => {
Babel.transform(fileContents, settings, (error, result) => {
if (error) {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
}
module.exports.BabelTransform = async (fileContents, settings, output) => {
let transformedData;
// Ensure the folder exists or create it
FsExtra.mkdirsSync(Path.dirname(output), {});
try {
transformedData = await Babel.transform(fileContents, settings);
} catch (error) {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
}
Fs.writeFile(
output,
result.code, // + os.EOL
{ encoding: 'utf8' },
(fsError) => {
if (fsError) {
// eslint-disable-next-line no-console
console.error(`${fsError}`);
process.exit(1);
}
},
);
});
// Ensure the folder exists or create it
await FsExtra.ensureDir(dirname(output), {});
await writeFile(
output,
transformedData.code, // + os.EOL
{ encoding: 'utf8' },
(fsError) => {
if (fsError) {
// eslint-disable-next-line no-console
console.error(`${fsError}`);
process.exit(1);
}
},
);
await createMinified(output.replace('.js', '.min.js'), transformedData.code);
};

View File

@ -1,5 +1,5 @@
const {
readdir, readFile, rename, writeFile, unlink,
readdir, readFile, writeFile, unlink,
} = require('fs').promises;
const { resolve } = require('path');
const { minify } = require('terser');
@ -13,11 +13,13 @@ const tasks = [];
const inputFolder = 'build/media_source/vendor/bootstrap/js';
const outputFolder = 'media/vendor/bootstrap/js';
const getCurrentUnixTime = Math.round((new Date()).getTime() / 1000);
const createMinified = async (file) => {
const initial = await readFile(resolve(outputFolder, file), { encoding: 'utf8' });
const mini = await minify(initial);
await rename(resolve(outputFolder, file), resolve(outputFolder, `${file.split('-')[0]}.es6.js`));
await writeFile(resolve(outputFolder, `${file.split('-')[0]}.es6.min.js`), mini.code, { encoding: 'utf8' });
const mini = await minify(initial.replace('./popper.js', `./popper.min.js?${getCurrentUnixTime}`).replace('./dom.js', `./dom.min.js?${getCurrentUnixTime}`), { sourceMap: false, format: { comments: false } });
await writeFile(resolve(outputFolder, file), initial.replace('./popper.js', `./popper.js?${getCurrentUnixTime}`).replace('./dom.js', `./dom.js?${getCurrentUnixTime}`), { encoding: 'utf8' });
await writeFile(resolve(outputFolder, file.replace('.js', '.min.js')), mini.code, { encoding: 'utf8' });
};
const build = async () => {
@ -67,7 +69,11 @@ const build = async () => {
format: 'es',
sourcemap: false,
dir: outputFolder,
chunkFileNames: '[name].js',
});
// closes the bundle
await bundle.close();
};
const buildLegacy = async () => {
@ -110,11 +116,14 @@ const buildLegacy = async () => {
format: 'iife',
sourcemap: false,
name: 'Bootstrap',
file: resolve(outputFolder, 'bootstrap.es5.js'),
file: resolve(outputFolder, 'bootstrap-es5.js'),
});
// closes the bundle
await bundle.close();
};
(async () => {
module.exports.bootstrapJs = async () => {
rimraf.sync(resolve(outputFolder));
try {
@ -142,9 +151,9 @@ const buildLegacy = async () => {
try {
await buildLegacy(inputFolder, 'index.es6.js');
const es5File = await readFile(resolve(outputFolder, 'bootstrap.es5.js'), { encoding: 'utf8' });
const mini = await minify(es5File);
await writeFile(resolve(outputFolder, 'bootstrap.es5.min.js'), mini.code, { encoding: 'utf8' });
const es5File = await readFile(resolve(outputFolder, 'bootstrap-es5.js'), { encoding: 'utf8' });
const mini = await minify(es5File, { sourceMap: false, format: { comments: false } });
await writeFile(resolve(outputFolder, 'bootstrap-es5.min.js'), mini.code, { encoding: 'utf8' });
// eslint-disable-next-line no-console
console.log('Legacy done! ✅');
} catch (error) {
@ -152,4 +161,4 @@ const buildLegacy = async () => {
console.error(error);
process.exit(1);
}
})();
};

View File

@ -1,11 +1,25 @@
const Fs = require('fs');
const Path = require('path');
const FsExtra = require('fs-extra');
const Babel = require('./babel-transform.es6.js');
const { lstat, readdir, readFile } = require('fs').promises;
const { dirname, sep } = require('path');
const { ensureDir } = require('fs-extra');
const { BabelTransform } = require('./babel-transform.es6.js');
const headerText = `PLEASE DO NOT MODIFY THIS FILE. WORK ON THE ES6 VERSION.
OTHERWISE YOUR CHANGES WILL BE REPLACED ON THE NEXT BUILD.`;
/**
* Check if a file exists
*
* @param file
* @returns {Promise<boolean>}
*/
const folderExists = async (folder) => {
try {
return (await lstat(folder)).isDirectory();
} catch (e) {
return false;
}
};
// Predefine some settings
const settings = [
{
@ -23,21 +37,6 @@ const settings = [
],
comments: true,
},
{
presets: [
['@babel/preset-env', {
targets: {
browsers: ['ie 11'],
},
modules: false,
}],
['minify', { builtIns: false }],
],
plugins: [
['@babel/plugin-transform-classes'],
],
comments: false,
},
{
presets: [
['@babel/preset-env', {
@ -52,19 +51,6 @@ const settings = [
],
comments: true,
},
{
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 5%', 'not ie 11'],
},
modules: false,
}],
['minify', { builtIns: false }],
],
plugins: [],
comments: false,
},
];
/**
@ -72,48 +58,44 @@ const settings = [
*
* @param file the full path to the file + filename + extension
*/
module.exports.compileFile = (file) => {
Promise.resolve()
.then(() => {
const filePath = file.slice(0, -7);
module.exports.handleESMFile = async (file) => {
const filePath = file.slice(0, -7);
const newPath = filePath.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);
const outputFiles = [
`${newPath}.js`,
`${newPath}.es6.js`,
];
const outputFiles = [
`${filePath.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\')}.js`,
`${filePath.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\')}.min.js`,
`${filePath.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\')}.es6.js`,
`${filePath.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\')}.es6.min.js`,
];
// Ensure that the directories exist or create them
ensureDir(dirname(file.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`)));
// Ensure that the directories exist or create them
FsExtra.mkdirsSync(Path.dirname(file).replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'), {});
// Get the contents of the ES-XXXX file
let es6File = await readFile(file, { encoding: 'utf8' });
const es6FileAsync = [];
const es6Subdir = file.replace('es6.js', 'es6');
const coreWeirdFolderExists = await folderExists(es6Subdir);
// Get the contents of the ES-XXXX file
let es6File = Fs.readFileSync(file, 'utf8');
const es6Subdir = file.replace('es6.js', 'es6');
if (coreWeirdFolderExists) {
const allCorefilesPromises = [];
const concatenateFileContents = async (es6SubFile) => {
es6FileAsync.push(await readFile(`${es6Subdir}/${es6SubFile}`, { encoding: 'utf8' }));
};
const es6SubFiles = await readdir(es6Subdir);
es6SubFiles.sort();
es6SubFiles.map((es6SubFile) => allCorefilesPromises.push(concatenateFileContents(es6SubFile)));
await Promise.all(allCorefilesPromises);
}
if (Fs.existsSync(es6Subdir)) {
const stats = Fs.lstatSync(es6Subdir);
if (es6FileAsync.length) {
es6File += es6FileAsync.join('');
}
if (stats.isDirectory()) {
const es6SubFiles = Fs.readdirSync(es6Subdir);
es6SubFiles.sort();
es6SubFiles.forEach((es6SubFile) => {
es6File += Fs.readFileSync(`${es6Subdir}/${es6SubFile}`, 'utf8');
});
}
}
settings.forEach((setting, index) => {
// eslint-disable-next-line no-console
console.error(`Transpiling ES6 file: ${file}`);
Babel.run(es6File, setting, outputFiles[index]);
});
})
// Handle errors
.catch((error) => {
const jobs = [];
settings.forEach((setting, index) => {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
});
console.error(`Transpiling ES6 file: ${file}`);
jobs.push(BabelTransform(es6File, setting, outputFiles[index]));
});
return Promise.all(jobs);
};

View File

@ -1,11 +1,12 @@
const Autoprefixer = require('autoprefixer');
const CssNano = require('cssnano');
const Fs = require('fs');
const { sep } = require('path');
const Postcss = require('postcss');
const Sass = require('sass');
const Babel = require('./babel-transform.es6.js');
const { BabelTransform } = require('./babel-transform.es6.js');
const createJsFiles = (inputFile, es6FileContents) => {
const createJsFiles = async (inputFile, es6FileContents) => {
// Define some settings
const settings = [
{
@ -18,17 +19,6 @@ const createJsFiles = (inputFile, es6FileContents) => {
],
comments: true,
},
{
presets: [
['@babel/preset-env', {
targets: {
browsers: ['last 1 Chrome version'],
},
}],
['minify', { builtIns: false }],
],
comments: false,
},
{
presets: [
['@babel/preset-env', {
@ -41,36 +31,21 @@ const createJsFiles = (inputFile, es6FileContents) => {
'@babel/plugin-transform-classes',
],
comments: true,
},
{
presets: [
['@babel/preset-env', {
targets: {
browsers: ['ie 11'],
},
}],
['minify', { builtIns: false }],
],
plugins: [
['@babel/plugin-transform-classes'],
],
comments: false,
},
];
const file = inputFile.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);
const outputFiles = [
inputFile.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.w-c.es6.js', '.js'),
inputFile.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.w-c.es6.js', '.min.js'),
inputFile.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.w-c.es6.js', '-es5.js'),
inputFile.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.w-c.es6.js', '-es5.min.js'),
file.replace('.w-c.es6.js', '.js'),
file.replace('.w-c.es6.js', '-es5.js'),
];
const tasks = [];
settings.forEach((setting, index) => {
Babel.run(es6FileContents, setting, outputFiles[index]);
tasks.push(BabelTransform(es6FileContents, setting, outputFiles[index]));
});
await Promise.all(tasks);
};
/**
@ -78,7 +53,7 @@ const createJsFiles = (inputFile, es6FileContents) => {
*
* @param file The full path to the file + filename + extension
*/
module.exports.compile = (inputFile) => {
module.exports.handleWCFile = async (inputFile) => {
Promise.resolve()
.then(() => {
// Get the contents of the ES-XXXX file

View File

@ -0,0 +1,18 @@
const { readFile, writeFile } = require('fs').promises;
const FsExtra = require('fs-extra');
const { dirname, sep } = require('path');
const { minify } = require('terser');
module.exports.handleES5File = async (file) => {
if (file.match(/\.es5\.js$/)) {
// ES5 file, we will copy the file and then minify it in place
// Ensure that the directories exist or create them
await FsExtra.ensureDir(dirname(file).replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`));
await FsExtra.copy(file, file.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`).replace('.es5.js', '.js'));
const fileContent = await readFile(file, { encoding: 'utf8' });
const content = await minify(fileContent, { sourceMap: false, format: { comments: false } });
await writeFile(file.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`).replace('.es5.js', '.min.js'), content.code, { encoding: 'utf8' });
// eslint-disable-next-line no-console
console.log(`Es5 file copied/minified: ${file}`);
}
};

View File

@ -1,24 +0,0 @@
const Fs = require('fs');
const FsExtra = require('fs-extra');
const Path = require('path');
const UglifyJS = require('uglify-es');
const TranspileJs = require('./compile-es6.es6.js');
const TranspileWc = require('./compile-w-c.es6.js');
module.exports.run = (file) => {
if (file.match(/\.js/) && file.match(/\.es6\.js/) && !file.match(/\.w-c\.es6\.js/)) {
// ES6 file so we need to transpile it
TranspileJs.compileFile(file);
} else if (file.match(/\.js/) && file.match(/\.es5\.js/)) {
// ES5 file, we will copy the file and then minify it in place
// Ensure that the directories exist or create them
FsExtra.mkdirsSync(Path.dirname(file).replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\'), {});
Fs.copyFileSync(file, file.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.es5.js', '.js'));
Fs.writeFileSync(file.replace('/build/media_source/', '/media/').replace('\\build\\media_source\\', '\\media\\').replace('.es5.js', '.min.js'), UglifyJS.minify(Fs.readFileSync(file, 'utf8')).code, { encoding: 'utf8' });
// eslint-disable-next-line no-console
console.log(`Es5 file copied/minified: ${file}`);
} else if (file.match(/\.js/) && file.match(/\.w-c\.es6\.js/)) {
// Web Component, so we need to transpile it
TranspileWc.compile(file);
}
};

View File

@ -1,54 +0,0 @@
const Fs = require('fs');
const Path = require('path');
const UglifyJS = require('uglify-es');
const walkSync = require('walk-sync');
const RootPath = process.cwd();
/**
* Method that will minify a set of vendor javascript files
*/
module.exports.compile = () => {
Promise.resolve()
.then(() => {
const folders = [
Path.join(RootPath, 'media/vendor/codemirror'),
Path.join(RootPath, 'media/vendor/punycode/js'),
Path.join(RootPath, 'media/vendor/webcomponentsjs'),
];
// Loop to get some text for the package.json
folders.forEach((folder) => {
const files = walkSync(folder, {
globs: ['**/*.js'],
includeBasePath: true,
ignore: [],
directories: false,
});
if (files.length) {
files.forEach(
(file) => {
if (file.match(/\.js/) && !file.match(/LICENSE\.md/)) {
// eslint-disable-next-line no-console
console.log(`Processing ES5 file: ${file}`);
// Write the file
Fs.writeFileSync(
file.replace('.js', '.min.js'),
UglifyJS.minify(Fs.readFileSync(file, 'utf8')).code,
{ encoding: 'utf8' },
);
}
},
);
}
});
})
// Handle errors
.catch((error) => {
// eslint-disable-next-line no-console
console.error(`${error}`);
process.exit(1);
});
};

View File

@ -76,7 +76,7 @@
{
"name": "bootstrap.es5",
"type": "script",
"uri": "bootstrap.es5.min.js",
"uri": "bootstrap-es5.min.js",
"dependencies": [
"core"
],
@ -88,7 +88,7 @@
{
"name": "bootstrap.alert",
"type": "script",
"uri": "alert.es6.min.js",
"uri": "alert.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -99,7 +99,7 @@
{
"name": "bootstrap.button",
"type": "script",
"uri": "button.es6.min.js",
"uri": "button.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -110,7 +110,7 @@
{
"name": "bootstrap.carousel",
"type": "script",
"uri": "carousel.es6.min.js",
"uri": "carousel.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -121,7 +121,7 @@
{
"name": "bootstrap.collapse",
"type": "script",
"uri": "collapse.es6.min.js",
"uri": "collapse.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -132,7 +132,7 @@
{
"name": "bootstrap.dropdown",
"type": "script",
"uri": "dropdown.es6.min.js",
"uri": "dropdown.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -143,7 +143,7 @@
{
"name": "bootstrap.modal",
"type": "script",
"uri": "modal.es6.min.js",
"uri": "modal.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -154,7 +154,7 @@
{
"name": "bootstrap.popover",
"type": "script",
"uri": "popover.es6.min.js",
"uri": "popover.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -165,7 +165,7 @@
{
"name": "bootstrap.scrollspy",
"type": "script",
"uri": "scrollspy.es6.min.js",
"uri": "scrollspy.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -176,7 +176,7 @@
{
"name": "bootstrap.tab",
"type": "script",
"uri": "tab.es6.min.js",
"uri": "tab.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -187,7 +187,7 @@
{
"name": "bootstrap.toast",
"type": "script",
"uri": "toast.es6.min.js",
"uri": "toast.min.js",
"dependencies": [
"bootstrap.es5"
],
@ -665,6 +665,7 @@
"licenseFilename": "LICENSE",
"js": {
"dist/metismenujs.js": "js/metismenujs.js",
"dist/metismenujs.js.map": "js/metismenujs.js.map",
"dist/metismenujs.min.js": "js/metismenujs.min.js",
"dist/metismenujs.min.js.map": "js/metismenujs.min.js.map"
},

View File

@ -0,0 +1,32 @@
const {
copy, readFile, writeFile, ensureDir,
} = require('fs-extra');
const { dirname, sep } = require('path');
const Postcss = require('postcss');
const Autoprefixer = require('autoprefixer');
const CssNano = require('cssnano');
module.exports.handleCssFile = async (file) => {
const outputFile = file.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);
try {
// CSS file, we will copy the file and then minify it in place
// Ensure that the directories exist or create them
await ensureDir(dirname(outputFile), { recursive: true, mode: 0o2644 });
if (file !== outputFile) {
await copy(file, outputFile, { overwrite: true });
}
const content = await readFile(file, { encoding: 'utf8' });
const cssMin = await Postcss([Autoprefixer, CssNano]).process(content, { from: undefined });
// Ensure the folder exists or create it
await writeFile(outputFile.replace('.css', '.min.css'), cssMin.css.toString(), { encoding: 'utf8', mode: 0o2644 });
// eslint-disable-next-line no-console
console.log(`CSS file copied/minified: ${file}`);
} catch (err) {
// eslint-disable-next-line no-console
console.log(err);
}
};

View File

@ -0,0 +1,47 @@
const Autoprefixer = require('autoprefixer');
const CssNano = require('cssnano');
const { writeFile } = require('fs').promises;
const { ensureDir } = require('fs-extra');
const { dirname, sep } = require('path');
const Postcss = require('postcss');
const Sass = require('sass');
module.exports.handleScssFile = async (file) => {
const cssFile = file.replace(`${sep}scss${sep}`, `${sep}css${sep}`)
.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`)
.replace('.scss', '.css');
let compiled;
try {
compiled = Sass.renderSync({ file });
} catch (error) {
// eslint-disable-next-line no-console
console.error(error.formatted);
process.exit(1);
}
// Auto prefixing
const cleaner = Postcss([Autoprefixer()]);
const res = await cleaner.process(compiled.css.toString(), { from: undefined });
// Ensure the folder exists or create it
await ensureDir(dirname(cssFile), {});
await writeFile(
cssFile,
res.css,
{ encoding: 'utf8', mode: 0o2644 },
);
const cssMin = await Postcss([CssNano]).process(res.css, { from: undefined });
// Ensure the folder exists or create it
await ensureDir(dirname(cssFile.replace('.css', '.min.css')), {});
await writeFile(
cssFile.replace('.css', '.min.css'),
cssMin.css,
{ encoding: 'utf8', mode: 0o2644 },
);
// eslint-disable-next-line no-console
console.log(`SCSS File compiled: ${cssFile}`);
};

View File

@ -1,15 +1,14 @@
const Autoprefixer = require('autoprefixer');
const CssNano = require('cssnano');
const Fs = require('fs');
const Fs = require('fs').promises;
const FsExtra = require('fs-extra');
const Path = require('path');
const { dirname, sep } = require('path');
const Postcss = require('postcss');
const Sass = require('sass');
module.exports.compile = (file) => {
const cssFile = file.replace('/scss/', '/css/').replace('\\scss\\', '\\css\\')
.replace('.scss', '.css').replace('/build/media_source/', '/media/')
.replace('\\build\\media_source\\', '\\media\\');
module.exports.compile = async (file) => {
const cssFile = file.replace(`${sep}scss${sep}`, `${sep}css${sep}`)
.replace('.scss', '.css').replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);
let compiled;
try {
@ -20,34 +19,34 @@ module.exports.compile = (file) => {
process.exit(1);
}
// forked.on('message', async (msg) => {
// console.log('Message from child', msg);
// Auto prefixing
const cleaner = Postcss(
[
Autoprefixer(),
],
const cleaner = Postcss([Autoprefixer()]);
const res = await cleaner.process(compiled.css.toString(), { from: undefined });
// Ensure the folder exists or create it
await FsExtra.mkdirs(dirname(cssFile), {});
await Fs.writeFile(
cssFile,
res.css.toString(),
{ encoding: 'utf8', mode: 0o2644 },
);
cleaner.process(compiled.css.toString(), { from: undefined })
.then((res) => {
// Ensure the folder exists or create it
FsExtra.mkdirsSync(Path.dirname(cssFile), {});
Fs.writeFileSync(
cssFile,
res.css.toString(),
{ encoding: 'utf8', mode: 0o2644 },
);
const cssMin = await Postcss([CssNano]).process(res.css.toString(), { from: undefined });
Postcss([CssNano]).process(res.css.toString(), { from: undefined }).then((cssMin) => {
// Ensure the folder exists or create it
FsExtra.mkdirsSync(Path.dirname(cssFile.replace('.css', '.min.css')), {});
Fs.writeFileSync(
cssFile.replace('.css', '.min.css'),
cssMin.css.toString(),
{ encoding: 'utf8', mode: 0o2644 },
);
// Ensure the folder exists or create it
FsExtra.mkdirs(dirname(cssFile.replace('.css', '.min.css')), {});
await Fs.writeFile(
cssFile.replace('.css', '.min.css'),
cssMin.css.toString(),
{ encoding: 'utf8', mode: 0o2644 },
);
// eslint-disable-next-line no-console
console.log(`SCSS File compiled: ${cssFile}`);
});
});
// eslint-disable-next-line no-console
console.log(`SCSS File compiled: ${cssFile}`);
// });
// forked.send({ file });
};

View File

@ -1,6 +1,8 @@
const watch = require('watch');
const Path = require('path');
const HandleJsFile = require('./javascript/handle-file.es6.js');
const { join, extname } = require('path');
const { handleWCFile } = require('./javascript/compile-w-c.es6.js');
const { handleESMFile } = require('./javascript/compile-es6.es6.js');
const { handleES5File } = require('./javascript/handle-es5.es6.js');
const RootPath = process.cwd();
@ -15,17 +17,34 @@ const RootPath = process.cwd();
// eslint-disable-next-line max-len, no-param-reassign, no-return-assign
const debounce = (callback, time = 250, interval) => (...args) => clearTimeout(interval, interval = setTimeout(callback, time, ...args));
module.exports.run = () => {
watch.createMonitor(Path.join(RootPath, 'build/media_source'), (monitor) => {
module.exports.watching = () => {
watch.createMonitor(join(RootPath, 'build/media_source'), (monitor) => {
monitor.on('created', (file) => {
if (file.match(/\.js/) && (file.match(/\.es5\.js/) || file.match(/\.es6\.js/) || file.match(/\.w-c\.es6\.js/))) {
debounce(HandleJsFile.run(file), 300);
if (extname(file) === '.js') {
if (file.match(/\.w-c\.es6\.js/)) {
debounce(handleWCFile(file), 300);
}
if (file.match(/\.es6\.js/)) {
debounce(handleESMFile(file), 300);
}
if (file.match(/\.es5\.js/)) {
debounce(handleES5File(file), 300);
}
}
// @todo css and scss
});
monitor.on('changed', (file) => {
if (file.match(/\.js/) && (file.match(/\.es5\.js/) || file.match(/\.es6\.js/) || file.match(/\.w-c\.es6\.js/))) {
debounce(HandleJsFile.run(file), 300);
if (extname(file) === '.js') {
if (file.match(/\.w-c\.es6\.js/)) {
debounce(handleWCFile(file), 300);
}
if (file.match(/\.es6\.js/)) {
debounce(handleESMFile(file), 300);
}
if (file.match(/\.es5\.js/)) {
debounce(handleES5File(file), 300);
}
}
// @todo css and scss
});

View File

@ -10,31 +10,61 @@
* node build.js --copy-assets === will clean the media/vendor folder and then will populate the folder from node_modules
* node build.js --compile-js === will transpile ES6 files and also uglify the ES6,ES5 files
* node build.js --compile-css === will compile all the scss defined files and also create a minified version of the css
* node build.js --gzip === will create gzip files for all the minified stylesheets and scripts.'
* node build.js --compile-bs === will compile all the Bootstrap javascript components
* node build.js --gzip === will create gzip files for all the minified stylesheets and scripts.'
*/
// eslint-disable-next-line import/no-extraneous-dependencies
const Program = require('commander');
const semver = require('semver');
// eslint-disable-next-line import/no-extraneous-dependencies
// Joomla Build modules
const errorPages = require('./build-modules-js/error-pages.es6.js');
const init = require('./build-modules-js/init.es6.js');
const compileCSS = require('./build-modules-js/compilecss.es6.js');
const compileJS = require('./build-modules-js/compilejs.es6.js');
const minifyVendor = require('./build-modules-js/javascript/minify-vendor.es6.js');
const watch = require('./build-modules-js/watch.es6.js');
const { gzipFiles } = require('./build-modules-js/gzip-assets.es6');
const { createErrorPages } = require('./build-modules-js/error-pages.es6.js');
const { stylesheets } = require('./build-modules-js/compilecss.es6.js');
const { scripts } = require('./build-modules-js/compilejs.es6.js');
const { bootstrapJs } = require('./build-modules-js/javascript/build-bootstrap-js.es6.js');
const { localisePackages } = require('./build-modules-js/init/localise-packages.es6.js');
const { minifyVendor } = require('./build-modules-js/init/minify-vendor.es6.js');
const { patchPackages } = require('./build-modules-js/init/patches.es6.js');
const { cleanVendors } = require('./build-modules-js/init/cleanup-media.es6.js');
const { recreateMediaFolder } = require('./build-modules-js/init/recreate-media.es6');
const { watching } = require('./build-modules-js/watch.es6.js');
const { compressFiles } = require('./build-modules-js/compress.es6.js');
// The settings
const options = require('../package.json');
const settings = require('./build-modules-js/settings.json');
// Simple timer
const timer = (name) => {
const start = new Date();
return {
stop: () => {
const end = new Date();
const time = end.getTime() - start.getTime();
console.log('Timer:', name, 'finished in', time, 'ms');
},
};
};
// Merge Joomla's specific settings to the main package.json object
if ('settings' in settings) {
options.settings = settings.settings;
}
const handleError = (err, terminateCode) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(terminateCode);
};
const allowedVersion = () => {
if (!semver.satisfies(process.version.substring(1), options.engines.node)) {
handleError(`Command line tools require Node Version ${options.engines.node} but found ${process.version}`, -1);
}
};
// Initialize the CLI
Program
.version(options.version)
@ -42,8 +72,10 @@ Program
.option('--build-pages', 'Creates the error pages for unsupported PHP version & incomplete environment')
.option('--compile-js, --compile-js path', 'Handles ES6, ES5 and web component scripts')
.option('--compile-css, --compile-css path', 'Compiles all the scss files to css')
.option('--compile-bs', 'Compiles all the Bootstrap component scripts.')
.option('--watch', 'Watch file changes and re-compile (ATM only works for the js in the media_source).')
.option('--gzip', 'Compress all the minified stylesheets and scripts.')
.option('--prepare', 'Run all the needed tasks to initialise the repo')
.on('--help', () => {
// eslint-disable-next-line no-console
console.log(`Version: ${options.version}`);
@ -51,61 +83,83 @@ Program
})
.parse(process.argv);
// Show help by default
if (!process.argv.slice(2).length) {
Program.outputHelp();
process.exit(1);
handleError('', 1);
}
// Update the vendor folder
if (Program.copyAssets) {
Promise.resolve()
.then(init.copyAssets(options))
.then(minifyVendor.compile(options))
// Exit with success
.then(() => process.exit(0))
// Handle errors
.catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(-1);
});
allowedVersion();
Promise.all([cleanVendors()])
.then(() => recreateMediaFolder())
.then(() => localisePackages(options))
.then(() => patchPackages(options))
.then(() => minifyVendor())
.then(() => {
process.exit(0);
})
.catch((error) => handleError(error, 1));
}
// Creates the error pages for unsupported PHP version & incomplete environment
if (Program.buildPages) {
Promise.resolve()
.then(() => {
errorPages.run(options);
})
// Handle errors
.catch((err) => {
// eslint-disable-next-line no-console
console.error(err);
process.exit(-1);
});
Promise.all([createErrorPages(options)])
.catch((err) => handleError(err, 1));
}
// Convert scss to css
if (Program.compileCss) {
compileCSS.compile(options, Program.args[0]);
Promise.all([stylesheets(options, Program.args[0])])
.catch((err) => handleError(err, 1));
}
// Compress/transpile the javascript files
if (Program.compileJs) {
compileJS.compileJS(options, Program.args[0]);
Promise.all([scripts(options, Program.args[0])])
.catch((err) => handleError(err, 1));
}
// Compress/transpile the javascript files
if (Program.watch) {
watch.run();
watching();
}
// Gzip js/css files
if (Program.compileBs) {
bootstrapJs();
}
// Gzip js/css files
if (Program.gzip) {
gzipFiles();
compressFiles();
}
// Prepare the repo for dev work
if (Program.prepare) {
(async () => {
const bench = timer('Build');
try {
allowedVersion();
await cleanVendors();
await recreateMediaFolder();
await localisePackages(options);
await patchPackages(options);
await Promise.all([
minifyVendor(),
createErrorPages(options),
stylesheets(options, Program.args[0]),
scripts(options, Program.args[0]),
bootstrapJs(),
]);
bench.stop();
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
process.exit(-1);
}
process.exit(0);
})();
}

5239
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,14 @@
"scripts": {
"build:js": "node build/build.js --compile-js",
"build:css": "node build/build.js --compile-css",
"build:bs5": "node build/build-modules-js/build-bootstrap-js.es6.js",
"build:bs5": "node build/build.js --compile-bs",
"watch": "node build/build.js --watch",
"lint:js": "eslint --config build/.eslintrc --ignore-pattern '/media/' --ext .es6.js,.es6,.vue .",
"lint:css": "stylelint --config build/.stylelintrc.json -s scss \"administrator/components/com_media/resources/**/*.scss\" \"administrator/templates/**/*.scss\" \"build/media_source/**/*.scss\" \"templates/**/*.scss\" \"installation/template/**/*.scss\"",
"test": "karma start tests/javascript/karma.conf.js --single-run",
"install": "node build/build.js --copy-assets && node build/build.js --build-pages && npm run build:bs5",
"postinstall": "node build/build.js --compile-js && node build/build.js --compile-css && npm run build:com_media",
"update": "node build/build.js --copy-assets && node build/build.js --build-pages && node build/build.js --compile-js && node build/build.js --compile-css",
"install": "node build/build.js --prepare",
"postinstall": "npm run build:com_media",
"update": "node build/build.js --copy-assets && node build/build.js --build-pages && node build/build.js --compile-js && node build/build.js --compile-css && node build/build.js --compile-bs && npm run build:com_media",
"gzip": "node build/build.js --gzip",
"watch:com_media": "cross-env NODE_ENV=development webpack --progress --hide-modules --watch --config administrator/components/com_media/webpack.config.js",
"dev:com_media": "cross-env NODE_ENV=development webpack --progress --hide-modules --config administrator/components/com_media/webpack.config.js",
@ -34,6 +34,7 @@
"dependencies": {
"@claviska/jquery-minicolors": "^2.3.5",
"@fortawesome/fontawesome-free": "^5.15.2",
"@popperjs/core": "^2.6.0",
"@webcomponents/webcomponentsjs": "^2.5.0",
"accessibility": "^3.0.10",
"awesomplete": "1.1.5",
@ -51,7 +52,6 @@
"jquery-migrate": "^3.3.2",
"mediaelement": "^4.2.16",
"metismenujs": "^1.2.1",
"@popperjs/core": "^2.6.0",
"punycode": "1.4.1",
"qrcode-generator": "^1.4.4",
"roboto-fontface": "^0.10.0",
@ -68,7 +68,7 @@
"@babel/plugin-transform-classes": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@gfx/zopfli": "^1.0.15",
"@dgrammatiko/compress": "^1.0.4",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^11.1.0",
"@rollup/plugin-replace": "^2.3.4",
@ -79,7 +79,6 @@
"babelify": "^10.0.0",
"browserify": "^16.5.2",
"commander": "^5.1.0",
"copy-dir": "^1.3.0",
"core-js": "^3.8.3",
"cross-env": "^7.0.3",
"css-loader": "^3.6.0",
@ -102,7 +101,6 @@
"karma-json-fixtures-preprocessor": "0.0.6",
"karma-verbose-reporter": "^0.0.6",
"mini-css-extract-plugin": "^0.9.0",
"path": "^0.12.7",
"postcss": "^7.0.35",
"recursive-readdir": "^2.2.2",
"rimraf": "^3.0.2",
@ -115,11 +113,8 @@
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.18.0",
"terser": "^5.5.1",
"uglify-es": "^3.3.9",
"uglifycss": "^0.0.29",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12",
"walk-sync": "^2.2.0",
"watch": "^1.0.2",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"