Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose PARAGON as a global variable #365

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
49b201a
feat: expose PARAGON_VERSION as a global variable
adamstankiewicz May 20, 2023
af90f45
fix: rely on paragon-theme.json from @edx/paragon
adamstankiewicz May 29, 2023
82c5614
chore: clean up and add typedefs
adamstankiewicz May 29, 2023
bbbf690
fix: create undefined PARAGON global variable if no compatible parago…
adamstankiewicz May 29, 2023
73ba105
fix: moar better error handling
adamstankiewicz May 29, 2023
0c34718
fix: updates
adamstankiewicz May 29, 2023
5f7d76f
fix: update based on new schema for paragon-theme.json
adamstankiewicz May 29, 2023
2a78f67
fix: update setupTest.js
adamstankiewicz May 29, 2023
d987b08
chore: clean up
adamstankiewicz Jun 3, 2023
cda3812
chore: remove compressionplugin
adamstankiewicz Jun 3, 2023
0e0dfcc
chore: quality
adamstankiewicz Jun 3, 2023
86bc18a
fix: rename paragon-theme.json to theme-urls.json
adamstankiewicz Jun 4, 2023
74a0a4f
chore: uninstall unused node_module
adamstankiewicz Jun 4, 2023
4daff04
feat: add @edx/brand version and urls to PARAGON_THEME global variable
adamstankiewicz Jun 4, 2023
4d94653
chore: update snapshot
adamstankiewicz Jun 5, 2023
1d17036
Merge branch 'master' into ags/2321
adamstankiewicz Jul 22, 2023
f9b47e7
fix: PR feedback
adamstankiewicz Jul 23, 2023
bd710ac
fix: add comment to ParagonWebpackPlugin and update snapshots
adamstankiewicz Jul 23, 2023
39cc967
feat: preload links from PARAGON_THEME_URLS config
adamstankiewicz Jul 24, 2023
782b91c
fix: handle undefined this.paragonMetadata
adamstankiewicz Jul 24, 2023
5a7af2e
fix: remove fallbackUrls
adamstankiewicz Jul 24, 2023
84c6d76
chore: snapshots and resolve .eslintrc error
adamstankiewicz Jul 24, 2023
091b34d
Merge branch 'master' into ags/2321
adamstankiewicz Dec 9, 2023
84cbeec
fix: updates
adamstankiewicz Dec 10, 2023
6f6f1ee
fix: typo in `alpha` CDN url within example env.config.js
adamstankiewicz Mar 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = {
},
globals: {
newrelic: false,
PARAGON: false,
},
ignorePatterns: [
'module.config.js',
Expand Down
157 changes: 157 additions & 0 deletions config/data/paragonUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const path = require('path');
const fs = require('fs');

/**
* Attempts to extract the Paragon version from the `node_modules` of
* the consuming application.
*
* @param {string} dir Path to directory containing `node_modules`.
* @returns {string} Paragon dependency version of the consuming application
*/
function getParagonVersion(dir) {
const pathToPackageJson = `${dir}/node_modules/@edx/paragon/package.json`;
if (!fs.existsSync(pathToPackageJson)) {
return undefined;
}
return JSON.parse(fs.readFileSync(pathToPackageJson)).version;
}

/**
* @typedef {Object} ParagonThemeCssAsset
* @property {string} filePath
* @property {string} entryName
* @property {string} outputChunkName
*/

/**
* @typedef {Object} ParagonThemeVariantCssAsset
* @property {string} filePath
* @property {string} entryName
* @property {string} outputChunkName
* @property {boolean} default
* @property {boolean} dark
*/

/**
* @typedef {Object} ParagonThemeCss
* @property {ParagonThemeCssAsset} core The metadata about the core Paragon theme CSS
* @property {Object.<string, ParagonThemeVariantCssAsset>} variants A collection of theme variants.
*/

/**
* Attempts to extract the Paragon theme CSS from the locally installed `@edx/paragon` package.
* @param {string} dir Path to directory containing `node_modules`.
* @returns {ParagonThemeCss}
*/
function getParagonThemeCss(dir) {
const pathToParagonThemeOutput = path.resolve(dir, './node_modules/@edx/paragon', 'dist', 'paragon-theme.json');
if (!fs.existsSync(pathToParagonThemeOutput)) {
return undefined;
}
const paragonConfig = JSON.parse(fs.readFileSync(pathToParagonThemeOutput));
const {
core: themeCore,
variants: themeVariants,
} = paragonConfig?.themeUrls || {};

const pathToCoreCss = path.resolve(dir, './node_modules/@edx/paragon', 'dist', themeCore.paths.minified);
const coreCssExists = fs.existsSync(pathToCoreCss);

const validThemeVariantPaths = Object.entries(themeVariants || {}).filter(([, value]) => {
const themeVariantCssDefault = path.resolve(dir, './node_modules/@edx/paragon', 'dist', value.paths.default);
const themeVariantCssMinified = path.resolve(dir, './node_modules/@edx/paragon', 'dist', value.paths.minified);
return fs.existsSync(themeVariantCssDefault) && fs.existsSync(themeVariantCssMinified);
});

if (!coreCssExists || validThemeVariantPaths.length === 0) {
return undefined;
}
const coreResult = {
filePath: path.resolve(dir, pathToCoreCss),
entryName: 'paragon.theme.core',
outputChunkName: 'paragon-theme-core',
};

const themeVariantResults = {};
validThemeVariantPaths.forEach(([themeVariant, value]) => {
themeVariantResults[themeVariant] = {
filePath: path.resolve(dir, './node_modules/@edx/paragon', 'dist', value.paths.minified),
entryName: `paragon.theme.variants.${themeVariant}`,
outputChunkName: `paragon-theme-variant-${themeVariant}`,
default: value.default,
dark: value.dark,
};
});

return {
core: fs.existsSync(pathToCoreCss) ? coreResult : undefined,
variants: themeVariantResults,
};
}

/**
* Replaces all periods in a string with hyphens.
* @param {string} str A string containing periods to replace with hyphens.
* @returns The input string with periods replaced with hyphens.
*/
function replacePeriodsWithHyphens(str) {
return str.replaceAll('.', '-');
}

/**
* @typedef CacheGroup
* @property {string} type The type of cache group.
* @property {string|function} name The name of the cache group.
* @property {function} chunks A function that returns true if the chunk should be included in the cache group.
* @property {boolean} enforce If true, this cache group will be created even if it conflicts with default cache groups.
*/

/**
* @param {ParagonThemeCss} paragonThemeCss The Paragon theme CSS metadata.
* @returns {Object.<string, CacheGroup>} The cache groups for the Paragon theme CSS.
*/
function getParagonCacheGroups(paragonThemeCss) {
const cacheGroups = {};
if (!paragonThemeCss) {
return cacheGroups;
}
cacheGroups[paragonThemeCss.core.entryName] = {
type: 'css/mini-extract',
name: replacePeriodsWithHyphens(paragonThemeCss.core.entryName),
chunks: chunk => chunk.name === paragonThemeCss.core.entryName,
enforce: true,
};
Object.values(paragonThemeCss.variants).forEach(({ entryName }) => {
cacheGroups[entryName] = {
type: 'css/mini-extract',
name: replacePeriodsWithHyphens(entryName),
chunks: chunk => chunk.name === entryName,
enforce: true,
};
});
return cacheGroups;
}

/**
* @param {ParagonThemeCss} paragonThemeCss The Paragon theme CSS metadata.
* @returns {Object.<string, string>} The entry points for the Paragon theme CSS.
*/
function getParagonEntryPoints(paragonThemeCss) {
const entryPoints = {};
if (!paragonThemeCss) {
return entryPoints;
}
entryPoints[paragonThemeCss.core.entryName] = path.resolve(process.cwd(), paragonThemeCss.core.filePath);
Object.values(paragonThemeCss.variants).forEach(({ filePath, entryName }) => {
entryPoints[entryName] = path.resolve(process.cwd(), filePath);
});
return entryPoints;
}

module.exports = {
getParagonVersion,
getParagonThemeCss,
getParagonCacheGroups,
getParagonEntryPoints,
replacePeriodsWithHyphens,
};
16 changes: 16 additions & 0 deletions config/jest/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,19 @@ const testEnvFile = path.resolve(process.cwd(), '.env.test');
if (fs.existsSync(testEnvFile)) {
dotenv.config({ path: testEnvFile });
}

global.PARAGON = {
version: '1.0.0',
themeUrls: {
core: {
fileName: 'core.min.css',
},
variants: {
light: {
fileName: 'light.min.css',
default: true,
dark: false,
},
},
},
};
23 changes: 23 additions & 0 deletions config/webpack.common.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
const path = require('path');
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');

const ParagonWebpackPlugin = require('../lib/plugins/paragon-webpack-plugin/ParagonWebpackPlugin');
const {
getParagonThemeCss,
getParagonCacheGroups,
getParagonEntryPoints,
} = require('./data/paragonUtils');

const paragonThemeCss = getParagonThemeCss(process.cwd());

module.exports = {
entry: {
app: path.resolve(process.cwd(), './src/index'),
...getParagonEntryPoints(paragonThemeCss),
adamstankiewicz marked this conversation as resolved.
Show resolved Hide resolved
},
output: {
path: path.resolve(process.cwd(), './dist'),
Expand All @@ -19,4 +30,16 @@ module.exports = {
},
extensions: ['.js', '.jsx'],
},
optimization: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the various chunks: ['app'] below, and this optimization block here too. What's the net effect of how we've changed the chunking? What do the cacheGroups do for us?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put another way, it may be worth a comment in the config here describing what we're doing to the optimization and how this differs from the default (I think you're leaving the chunking the same and just adding this caching thing, but that's only cause I've stared at a lot of these in my day 😉)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the chunking is staying the same as is it today, with the addition of splitting out the "locally" installed already-compiled CSS files from @edx/paragon and @edx/brand. I will add a comment :)

That said, we probably should find some ways to optimize our MFE Webpack bundles (e.g., test hypothesis that many small JS files for the node_modules is better than 1 larger JS file, recommend/implement a strategy for code splitting for MFEs): openedx/wg-frontend#173

splitChunks: {
chunks: 'all',
cacheGroups: {
...getParagonCacheGroups(paragonThemeCss),
},
},
},
plugins: [
new RemoveEmptyScriptsPlugin(),
new ParagonWebpackPlugin(),
],
};
1 change: 1 addition & 0 deletions config/webpack.dev-stage.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ module.exports = merge(commonConfig, {
new HtmlWebpackPlugin({
inject: true, // Appends script tags linking to the webpack bundles at the end of the body
template: path.resolve(process.cwd(), 'public/index.html'),
chunks: ['app'],
FAVICON_URL: process.env.FAVICON_URL || null,
OPTIMIZELY_PROJECT_ID: process.env.OPTIMIZELY_PROJECT_ID || null,
NODE_ENV: process.env.NODE_ENV || null,
Expand Down
88 changes: 55 additions & 33 deletions config/webpack.dev.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This is the dev Webpack config. All settings here should prefer a fast build
// time at the expense of creating larger, unoptimized bundles.
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { merge } = require('webpack-merge');
const Dotenv = require('dotenv-webpack');
const dotenv = require('dotenv');
Expand Down Expand Up @@ -31,6 +31,45 @@ resolvePrivateEnvConfig('.env.private');
const aliases = getLocalAliases();
const PUBLIC_PATH = process.env.PUBLIC_PATH || '/';

function getStyleUseConfig() {
return [
{
loader: 'css-loader', // translates CSS into CommonJS
options: {
sourceMap: true,
modules: {
compileType: 'icss',
},
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
PostCssAutoprefixerPlugin(),
PostCssRTLCSS(),
PostCssCustomMediaCSS(),
],
},
},
},
'resolve-url-loader',
{
loader: 'sass-loader', // compiles Sass to CSS
options: {
sourceMap: true,
sassOptions: {
includePaths: [
path.join(process.cwd(), 'node_modules'),
path.join(process.cwd(), 'src'),
],
},
},
},
];
}

module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'eval-source-map',
Expand Down Expand Up @@ -68,41 +107,19 @@ module.exports = merge(commonConfig, {
// flash-of-unstyled-content issues in development.
{
test: /(.scss|.css)$/,
use: [
'style-loader', // creates style nodes from JS strings
oneOf: [
{
loader: 'css-loader', // translates CSS into CommonJS
options: {
sourceMap: true,
modules: {
compileType: 'icss',
},
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
PostCssAutoprefixerPlugin(),
PostCssRTLCSS(),
PostCssCustomMediaCSS(),
],
},
},
resource: /paragon/,
use: [
MiniCssExtractPlugin.loader,
...getStyleUseConfig(),
],
},
'resolve-url-loader',
{
loader: 'sass-loader', // compiles Sass to CSS
options: {
sourceMap: true,
sassOptions: {
includePaths: [
path.join(process.cwd(), 'node_modules'),
path.join(process.cwd(), 'src'),
],
},
},
use: [
'style-loader', // creates style nodes from JS strings
...getStyleUseConfig(),
],
},
],
},
Expand Down Expand Up @@ -154,10 +171,15 @@ module.exports = merge(commonConfig, {
},
// Specify additional processing or side-effects done on the Webpack output bundles as a whole.
plugins: [
// Writes the extracted CSS from each entry to a file in the output directory.
new MiniCssExtractPlugin({
filename: '[name].css',
}),
// Generates an HTML file in the output directory.
new HtmlWebpackPlugin({
inject: true, // Appends script tags linking to the webpack bundles at the end of the body
template: path.resolve(process.cwd(), 'public/index.html'),
chunks: ['app'],
FAVICON_URL: process.env.FAVICON_URL || null,
OPTIMIZELY_PROJECT_ID: process.env.OPTIMIZELY_PROJECT_ID || null,
NODE_ENV: process.env.NODE_ENV || null,
Expand Down
3 changes: 2 additions & 1 deletion config/webpack.prod.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ module.exports = merge(commonConfig, {
plugins: [
PostCssAutoprefixerPlugin(),
PostCssRTLCSS(),
CssNano(),
PostCssCustomMediaCSS(),
CssNano(),
...extraPostCssPlugins,
],
},
Expand Down Expand Up @@ -201,6 +201,7 @@ module.exports = merge(commonConfig, {
new HtmlWebpackPlugin({
inject: true, // Appends script tags linking to the webpack bundles at the end of the body
template: path.resolve(process.cwd(), 'public/index.html'),
chunks: ['app'],
FAVICON_URL: process.env.FAVICON_URL || null,
OPTIMIZELY_PROJECT_ID: process.env.OPTIMIZELY_PROJECT_ID || null,
NODE_ENV: process.env.NODE_ENV || null,
Expand Down
1 change: 1 addition & 0 deletions example/.env.development
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
BASE_URL='http://localhost:8080'
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
TEST_VARIABLE='foo'
Loading