├── .nvmrc
├── config
├── stats.config.js
├── devserver.config.js
├── cache.config.js
├── postcss.config.js
├── performance.config.js
├── eslint.config.js
├── stylelint.config.js
├── output.config.js
├── resolve.config.js
├── babel.config.js
├── templates.config.js
├── sass.config.js
├── clientlibs.config.js
├── plugins.config.js
├── index.js
├── optimization.config.js
└── general.config.js
├── cli
└── index.js
├── test
├── src
│ ├── common
│ │ ├── styles
│ │ │ ├── _variables.scss
│ │ │ └── _media-queries.scss
│ │ └── utils
│ │ │ ├── random.js
│ │ │ ├── randomInt.js
│ │ │ └── math.js
│ ├── author
│ │ ├── author.src.scss
│ │ └── author.src.js
│ ├── publish
│ │ ├── component
│ │ │ └── component.src.scss
│ │ ├── publish.src.scss
│ │ └── publish.src.js
│ └── minimal
│ │ ├── .febuild
│ │ └── .febuild.js
├── .eslintrc.json
├── index.html
├── stylelint.config.js
├── .febuild
└── .febuild.js
├── docs
├── images
│ └── task-webpack.png
├── tasks.md
├── CONTRIBUTING.md
├── quick_start.md
├── CODE_OF_CONDUCT.md
├── migration.md
├── configuration.md
└── CHANGELOG.md
├── .npmignore
├── .editorconfig
├── utils
├── linterError.js
├── getClientlib.js
├── mkFullPathSync.js
├── getArgumentValue.js
├── linterError.test.js
├── checkChunk.js
├── log.test.js
├── writeFile.js
├── getPlugins.js
├── mkFullPathSync.test.js
├── renderStyles.js
├── merge.js
├── getClientlib.test.js
├── generateEntries.js
├── checkChunk.test.js
├── runStylelint.js
├── getArgumentValue.test.js
├── merge.test.js
├── taskVerification.js
├── extendConfig.js
├── renderPostcss.js
├── renderClientLibs.js
├── getPlugins.test.js
├── generateEntries.test.js
├── extendConfig.test.js
├── log.js
├── renderSass.js
└── renderPostcss.test.js
├── .releaserc
├── .github
├── ISSUE_TEMPLATE.md
├── workflows
│ ├── ci.yml
│ ├── release.yml
│ ├── codeql-analysis.yml
│ └── manual-release.yml
└── PULL_REQUEST_TEMPLATE.md
├── hooks
└── pre-commit
├── SECURITY.md
├── .gitignore
├── tasks
├── styles.test.js
├── clientlibs.js
├── webpack.test.js
├── run.js
├── clientlibs.test.js
├── styles.js
└── webpack.js
├── package.json
├── README.md
└── LICENSE
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16
2 |
--------------------------------------------------------------------------------
/config/stats.config.js:
--------------------------------------------------------------------------------
1 | module.exports = 'verbose';
--------------------------------------------------------------------------------
/config/devserver.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | }
--------------------------------------------------------------------------------
/cli/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('../tasks/run');
3 |
--------------------------------------------------------------------------------
/config/cache.config.js:
--------------------------------------------------------------------------------
1 | // webpack cache
2 | module.exports = true
--------------------------------------------------------------------------------
/test/src/common/styles/_variables.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #5a3699;
2 | $secondary-color: #fff;
3 |
--------------------------------------------------------------------------------
/config/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['autoprefixer'],
3 | failOnError: true
4 | };
5 |
--------------------------------------------------------------------------------
/docs/images/task-webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Netcentric/fe-build/HEAD/docs/images/task-webpack.png
--------------------------------------------------------------------------------
/config/performance.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | maxAssetSize: 100000,
3 | maxEntrypointSize: 350000,
4 | };
--------------------------------------------------------------------------------
/config/eslint.config.js:
--------------------------------------------------------------------------------
1 | // eslint plugin configuration
2 | module.exports = {
3 | failOnError: true,
4 | fix: true
5 | };
--------------------------------------------------------------------------------
/config/stylelint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Stops build if a linter error is found
3 | failOnError: true,
4 | };
5 |
--------------------------------------------------------------------------------
/test/src/common/utils/random.js:
--------------------------------------------------------------------------------
1 | export function random(min = 0, max = 1) {
2 | return Math.random() * (max - min) + min;
3 | }
--------------------------------------------------------------------------------
/test/src/common/utils/randomInt.js:
--------------------------------------------------------------------------------
1 |
2 | export function randomInt(min = 0, max = 1) {
3 | return Math.round(Math.random() * (max - min) + min);
4 | }
5 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "env": {
4 | "browser": true
5 | },
6 | "parserOptions": {
7 | "ecmaVersion": 2020,
8 | "sourceType": "module"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/src/common/utils/math.js:
--------------------------------------------------------------------------------
1 | export function randomInt(min = 0, max = 1) {
2 | return Math.round(random(min, max));
3 | }
4 |
5 | export function random(min = 0, max = 1) {
6 | return Math.random() * (max - min) + min;
7 | }
8 |
--------------------------------------------------------------------------------
/config/output.config.js:
--------------------------------------------------------------------------------
1 | const path = require('./general.config').destinationPath;
2 |
3 | module.exports = {
4 | path,
5 | filename: '[name]',
6 | chunkFilename: '[name]',
7 | publicPath: '/',
8 | pathinfo: false
9 | };
10 |
--------------------------------------------------------------------------------
/config/resolve.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const {
4 | common,
5 | nodeModules
6 | } = require('./general.config');
7 |
8 | module.exports = {
9 | alias: {
10 | utils: path.join(common, 'utils'),
11 | },
12 | modules: [nodeModules]
13 | };
14 |
--------------------------------------------------------------------------------
/test/src/author/author.src.scss:
--------------------------------------------------------------------------------
1 | @use 'media-queries' as mq;
2 |
3 | body {
4 | @include mq.for-size(s) {
5 | font-size: 1.5em;
6 |
7 |
8 |
9 | }
10 |
11 | @include mq.for-size(m) {
12 | font-size: 1em;
13 | }
14 | }
15 |
16 | ::placeholder {
17 | color: gray;
18 | }
--------------------------------------------------------------------------------
/test/src/publish/component/component.src.scss:
--------------------------------------------------------------------------------
1 | ::placeholder {
2 | color: gray;
3 | }
4 |
5 | .image {
6 | background-image: url("image@1x.png");
7 | }
8 |
9 | @media (resolution >= 2dppx) {
10 | .image {
11 | background-image: url("image@2x.png");
12 | }
13 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .eslintrc
2 | .nyc_output
3 | rollup.config.js
4 | .prettierrc
5 | .git
6 | .prettierignore
7 | **/.git/
8 | **/node_modules/
9 | yarn.lock
10 | /dev
11 | /docs
12 | /public
13 | /coverage
14 | /.vscode
15 | /tests
16 | /temp
17 | /templates
18 | /demos
19 | /src
20 | .github
21 | /test
22 |
--------------------------------------------------------------------------------
/test/src/publish/publish.src.scss:
--------------------------------------------------------------------------------
1 | @use 'variables';
2 |
3 | body {
4 | color: variables.$secondary-color;
5 | background-color: variables.$primary-color;
6 |
7 | @media (min-resolution: 2dppx) {
8 | .image {
9 | background-image: url('image@2x.png');
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # Unix-style newlines with a newline ending every file
4 | [*]
5 | end_of_line = lf
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | charset = utf-8
9 | indent_style = space
10 | indent_size = 2
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/test/src/minimal/.febuild:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | babel: {
5 | use: {
6 | options: {
7 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, debug: true }]]
8 | }
9 | }
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/test/src/minimal/.febuild.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | babel: {
5 | use: {
6 | options: {
7 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, debug: true }]]
8 | }
9 | }
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/config/babel.config.js:
--------------------------------------------------------------------------------
1 | // Webpack configuration
2 | module.exports = {
3 | enforce: 'post',
4 | test: /\.js$/,
5 | exclude: /node_modules\/(?!@netcentric)/,
6 | use: {
7 | loader: 'babel-loader',
8 | options: {
9 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]],
10 | plugins: ['@babel/plugin-transform-runtime']
11 | }
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/utils/linterError.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { log, color } = require('./log');
3 |
4 | module.exports = function linterError(lintError, fileError) {
5 | const location = `${(fileError.source)}:${lintError.line}:${lintError.column} \n`;
6 | log(__filename, `
7 | ${lintError.text}${color('reset', '')}
8 | ${color('dim', `LintError at ${location}`)}`, '', 'error', true);
9 | };
10 |
--------------------------------------------------------------------------------
/config/templates.config.js:
--------------------------------------------------------------------------------
1 |
2 | const clientlibTemplate = (category, prefix) => `
3 |
7 | `;
8 |
9 | module.exports = {
10 | clientlibTemplate
11 | };
12 |
--------------------------------------------------------------------------------
/utils/getClientlib.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function getClientlib(original) {
4 |
5 | const folder = path.dirname(original);
6 | const fileName = path.basename(original);
7 | const name = folder.split(path.sep).join('.');
8 | const extension = original.split('.').pop();
9 |
10 | return {
11 | folder,
12 | name,
13 | fileName,
14 | extension,
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/utils/mkFullPathSync.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | module.exports = function mkFullPathSync(absolutePath, permissions = '0755') {
5 | // if is not set to not override
6 | absolutePath.split(path.sep).reduce((origin, folder) => {
7 | const next = `${origin}${folder}${path.sep}`;
8 | if (!fs.existsSync(next)) fs.mkdirSync(next, permissions);
9 | return next;
10 | }, '');
11 | };
12 |
--------------------------------------------------------------------------------
/test/src/author/author.src.js:
--------------------------------------------------------------------------------
1 | import { randomInt } from "common/utils/randomInt";
2 | import { random } from "common/utils/random";
3 |
4 | class MainAuthor {
5 | constructor() {
6 | this.value = randomInt(1, 10);
7 | this.value2 = random(1, 10);
8 | this.init();
9 | }
10 |
11 | init() {
12 | console.log(`Hello Author ${this.value} author ${this.value2}!`);
13 | }
14 | }
15 |
16 | export const instace = new MainAuthor();
17 |
--------------------------------------------------------------------------------
/config/sass.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { isProduction, common, nodeModules } = require('./general.config');
3 |
4 | const includePaths = [path.join(common, 'styles'), nodeModules];
5 | const outputStyle = isProduction ? 'compressed' : 'expanded';
6 |
7 | module.exports = {
8 | includePaths,
9 | outputStyle,
10 | // for any https://sass-lang.com/documentation/js-api/interfaces/options/ options
11 | adicionalOptions: {}
12 | };
13 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/src/common/styles/_media-queries.scss:
--------------------------------------------------------------------------------
1 | @mixin for-size($size) {
2 | @if $size == s {
3 | @media (min-width: 600px) {
4 | @content;
5 | }
6 | } @else if $size == m {
7 | @media (min-width: 768px) {
8 | @content;
9 | }
10 | } @else if $size == l {
11 | @media (min-width: 960px) {
12 | @content;
13 | }
14 | } @else if $size == xl {
15 | @media (min-width: 1200px) {
16 | @content;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/src/publish/publish.src.js:
--------------------------------------------------------------------------------
1 | import { randomInt } from "common/utils/randomInt";
2 |
3 | class MainPublish {
4 | constructor() {
5 | this.value = randomInt(1, 10);
6 | this.arr = [...[0, 1, 2]];
7 | this.obj = {
8 | name: 'test'
9 | };
10 | this.init();
11 | }
12 |
13 | init() {
14 | const { name } = this.obj;
15 | console.log(`Hello World ${this.value}! ${name}`);
16 | }
17 | }
18 | export const instace = new MainPublish();
19 |
--------------------------------------------------------------------------------
/.releaserc:
--------------------------------------------------------------------------------
1 | {
2 | "branches": ["main"],
3 | "plugins": [
4 | "@semantic-release/commit-analyzer",
5 | "@semantic-release/release-notes-generator",
6 | [
7 | "@semantic-release/changelog",
8 | {
9 | "changelogFile": "docs/CHANGELOG.md"
10 | }
11 | ],
12 | "@semantic-release/npm",
13 | [
14 | "@semantic-release/git",
15 | {
16 | "assets": ["docs/CHANGELOG.md", "package.json"]
17 | }
18 | ]
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Expected Behaviour
4 |
5 | ### Actual Behaviour
6 |
7 | ### Reproduce Scenario (including but not limited to)
8 |
9 | #### Steps to Reproduce
10 |
11 | #### Platform and Version
12 |
13 | #### Sample Code that illustrates the problem
14 |
15 | #### Logs taken while reproducing problem
16 |
--------------------------------------------------------------------------------
/hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "***** ******"
3 | echo "***** Cognizant Netcentric (https://www.netcentric.biz) ******"
4 | echo "***** ******"
5 | echo "***** Running jest test :) ******"
6 | echo "***** ******"
7 |
8 | git stash -q --keep-index
9 |
10 | npm test
11 |
12 | status=$?
13 |
14 | git stash pop -q
15 |
16 | exit $status
--------------------------------------------------------------------------------
/utils/getArgumentValue.js:
--------------------------------------------------------------------------------
1 | module.exports = (key) => {
2 | if (process.argv) {
3 | // get the last argument of the key
4 | const args = Array.from(process.argv)
5 | .filter(arg => arg.indexOf(key) >= 0).pop();
6 |
7 | if (args) {
8 | // send the value if there is a value pass
9 | if (args.length !== key.length) {
10 | return args.split(key).pop();
11 | }
12 |
13 | // return true if there is no value just the argument
14 | return true;
15 | }
16 |
17 | return false;
18 | }
19 |
20 | return false;
21 | };
22 |
--------------------------------------------------------------------------------
/utils/linterError.test.js:
--------------------------------------------------------------------------------
1 | const linterError = require("./linterError");
2 | const path = require('path');
3 | process.argv.push('--quiet');
4 |
5 | describe('Test utils/linterError.js', () => {
6 | it('Check formating of a error send to console log', () => {
7 | const source = __filename;
8 | const line = 12;
9 | const column = 10;
10 | const text = 'my error'
11 | console.error = jest.fn();
12 | linterError({line, column, text}, {source});
13 | expect(console.error.mock.calls[0][0]).toMatchSnapshot();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Here below there is the list of versions of the fe-build which are currently being supported with security updates.
6 |
7 | | Version | Supported |
8 | | ------- | ------------------ |
9 | | 2.0.x | :white_check_mark: |
10 | | < 2.0.0 | :x: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | To report a vulnerability please send us an email with the details to opensource@netcentric.biz.
15 | This will help us to assess the risk and start the necessary steps.
16 |
17 | Please **do not** file a GitHub issue for a vulnerability.
18 |
--------------------------------------------------------------------------------
/test/stylelint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['stylelint-config-standard-scss'],
3 | ignoreFiles: [
4 | '**/*.css',
5 | '**/*.js',
6 | '**/node_modules/**'
7 | ],
8 | rules: {
9 | 'selector-type-no-unknown': [true, {
10 | ignore: [
11 | 'custom-elements',
12 | 'default-namespace'
13 | ]
14 | }],
15 | 'media-feature-range-notation': null,
16 | 'at-rule-no-unknown': [true, {
17 | ignoreAtRules: ['use', 'function', 'if', 'each', 'else', 'for', 'include', 'mixin', 'return', 'warn']
18 | }]
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/utils/checkChunk.js:
--------------------------------------------------------------------------------
1 | const includesInModules = (names = []) => (module) => names.filter((name) => module.includes(name)).length > 0;
2 |
3 | // To check if a context is from a vendor
4 | module.exports = function checkChunk(module, excludes = [], includes = []) {
5 | // is not a external module so there is no context
6 | if (!module) {
7 | return false;
8 | }
9 |
10 | if (includesInModules(excludes)(module)) {
11 | return false;
12 | }
13 | // has includes defined and the module is not in the includes
14 | if (includes.length === 0 ) {
15 | return true;
16 | }
17 |
18 | return includesInModules(includes)(module);
19 | };
20 |
--------------------------------------------------------------------------------
/config/clientlibs.config.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | clientLibs generation configuration
4 | All other configurations
5 |
6 | */
7 |
8 | // Override all clientlibs
9 | const override = false;
10 |
11 | // array of categories to skip (if override is true)
12 | const skipCategories = ['myproject.author'];
13 | // extra XML params to append to .content.xml use key=value
14 | // linter disabled since we are requirement to send $\{ to a template string
15 |
16 | // Object to be able to generate clientlibs for new CSS files (created in build process) in target folder
17 | const extraEntries = {};
18 |
19 | module.exports = {
20 | override,
21 | skipCategories,
22 | extraEntries,
23 | };
24 |
--------------------------------------------------------------------------------
/config/plugins.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const { mode, analyze, analyzerPort } = require('./general.config');
3 |
4 | // pass the mode forward
5 | const environment = new webpack.DefinePlugin({
6 | 'process.env.NODE_ENV': JSON.stringify(mode)
7 | });
8 |
9 | // default plugins
10 | const plugins = [environment];
11 |
12 | // check each files / dependencies sizes
13 | // src https://www.npmjs.com/package/webpack-bundle-analyzer
14 | if (analyze) {
15 | const BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
16 | const analyzer = new BundleAnalyzer({
17 | analyzerPort
18 | });
19 |
20 | plugins.push(analyzer);
21 | }
22 |
23 | module.exports = plugins;
24 |
--------------------------------------------------------------------------------
/utils/log.test.js:
--------------------------------------------------------------------------------
1 | const { severity, log } = require("./log");
2 |
3 | describe('Test utils/log.js', () => {
4 | Object.keys(severity).forEach((severetyName,index) => {
5 | it(`Check formating of each error severety send to console log [${severetyName}]`, () => {
6 | const fn = severetyName === 'error' ? severetyName : 'log';
7 | const file = __filename;
8 | const title = 'My error';
9 | const extra = 'Extra error information';
10 | const text = 'my error';
11 | console[fn] = jest.fn();
12 | log(file, title, extra, severetyName);
13 | expect(console[fn].mock.calls[0][0]).toMatchSnapshot();
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/utils/writeFile.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { log } = require('./log');
3 | const path = require('path');
4 |
5 | module.exports = function writeFile(filepath, content, override = false) {
6 | const normalizePath = path.normalize(filepath);
7 | // if is not set to not override
8 | if (!override && fs.existsSync(normalizePath)) return;
9 | const dir = path.dirname(normalizePath);
10 | if (!fs.existsSync(dir)){
11 | fs.mkdirSync(dir, { recursive: true });
12 | }
13 | // white it
14 | fs.writeFileSync(normalizePath, content,
15 | err => log(__filename, 'error', `could not write file: ${err.Error},'\n ${err.path}`, 'error'));
16 |
17 | log(__filename, `write file...${normalizePath}`, '', 'info');
18 | };
19 |
--------------------------------------------------------------------------------
/utils/getPlugins.js:
--------------------------------------------------------------------------------
1 | module.exports = function getPlugins(plugins = []) {
2 | return plugins
3 | .filter(Boolean)
4 | .map(function (entry) {
5 | if (Array.isArray(entry)) {
6 | const [pluginName, pluginOptions] = entry;
7 | const pluginModule = require(pluginName);
8 | const pluginFn = pluginModule.default || pluginModule;
9 | return pluginFn(pluginOptions);
10 | } else if (typeof entry === 'string') {
11 | const pluginModule = require(entry);
12 | return pluginModule.default || pluginModule;
13 | } else {
14 | throw new Error(`Invalid plugin entry: ${JSON.stringify(entry)}`);
15 | }
16 | });
17 | };
--------------------------------------------------------------------------------
/utils/mkFullPathSync.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 | const mkdirSync = require("./mkFullPathSync");
3 | const fs = require('fs');
4 |
5 | const folderPath = './dist/mkFullPathSync/mkFullPathSyncExample'
6 |
7 |
8 | describe('Test utils/mkFullPathSync.js', () => {
9 | it(`It should check and create a recursive folder even if it does not exists`, () => {
10 | return new Promise((resolve) => {
11 | const removed = fs.rmSync(folderPath,{ recursive: true, force: true });
12 | const dir = mkdirSync(folderPath);
13 | resolve(folderPath);
14 | }).then(() => {
15 | expect(fs.existsSync(folderPath)).toBe(true);
16 | const removed = fs.rmSync('./dist',{ recursive: true, force: true });
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/utils/renderStyles.js:
--------------------------------------------------------------------------------
1 | const renderSass = require('../utils/renderSass');
2 | const renderPostcss = require('../utils/renderPostcss');
3 | const runStylelint = require('../utils/runStylelint');
4 | const writeFile = require('../utils/writeFile');
5 |
6 | module.exports = async function renderStyles(file, dest, config) {
7 | return new Promise((resolve) => {
8 | runStylelint(
9 | [file], config, () =>
10 | renderSass(
11 | dest, file, config, (result, outFile) =>
12 | renderPostcss(
13 | result, outFile, config, postCssResult => {
14 | // only write file at the end
15 | writeFile(outFile, postCssResult.css, true);
16 | resolve();
17 | }
18 | )
19 | )
20 | );
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # IDE
6 | .project
7 | .classpath
8 | .settings
9 | .idea/
10 | *.iml*
11 | *.ipr
12 | .brackets.json
13 | .vscode/
14 | .history
15 |
16 | # Maven
17 | target/
18 |
19 | # File Vault (VLT) / Sync
20 | .vlt
21 | .vlt-sync*
22 |
23 | # Mac OS X
24 | .DS_Store
25 | *.swp
26 |
27 | # git
28 | *.orig
29 |
30 | # Node related files
31 | node_modules/
32 | node/
33 | bin/
34 | typings
35 | npm-debug.log*
36 | yarn-debug.log*
37 | yarn-error.log*
38 | .stylelintcache
39 | .eslintcache
40 |
41 | # env
42 | \.env
43 |
44 | # bundle css and js files and maps
45 | *.bundle.*
46 | *.chunk.*
47 | symbols.svg
48 | .tmp/
49 | .cache-loader
50 | dist/
51 |
52 | # Svg Icons Temp folder
53 | tmp/
54 | coverage/
55 |
56 | # coverage folder
57 | coverage/
58 |
59 | # Bundle Analyser
60 | frontend/stats.json
61 | __snapshots__/
62 |
--------------------------------------------------------------------------------
/utils/merge.js:
--------------------------------------------------------------------------------
1 | // fast simplified version of a merge deep
2 | // only merge object properties not values / arrays.
3 | module.exports = function merge(original = {}, newObject) {
4 | const copy = Object.assign({}, original);
5 | const keys = Object.keys(newObject);
6 |
7 | keys.forEach((prop) => {
8 | if (newObject[prop]
9 | && typeof newObject[prop] === 'object'
10 | && !Array.isArray(newObject[prop])
11 | && !(newObject[prop] instanceof RegExp)) {
12 | copy[prop] = merge(original[prop], newObject[prop]);
13 | } else if (Array.isArray(newObject[prop])) {
14 | copy[prop] = newObject[prop] ? [...newObject[prop]] : [...original[prop]];
15 | } else {
16 | copy[prop] = newObject[prop] || typeof newObject[prop] === 'boolean' ? newObject[prop] : original[prop];
17 | }
18 | });
19 |
20 | return copy;
21 | };
22 |
--------------------------------------------------------------------------------
/utils/getClientlib.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 | const path = require('path');
3 | const getClientlib = require("./getClientlib");
4 |
5 | describe('Test utils/getClientlib.js', () => {
6 |
7 | it('Should return from a file location the required info for a clientlib, eg category name, file to import into js.txt etc', () => {
8 | const fileName = 'component.bundle.js';
9 | const folder = 'publish/content/component';
10 | const extension = 'js';
11 | const location = `${folder}${path.sep}${fileName}`;
12 | const clientlibNameCategory = 'publish.content.component';
13 | const clientlib = getClientlib(location);
14 | expect(clientlib.name).toBe(clientlibNameCategory);
15 | expect(clientlib.fileName).toBe(fileName);
16 | expect(clientlib.folder).toBe(folder);
17 | expect(clientlib.extension).toBe(extension);
18 | });
19 | })
20 |
--------------------------------------------------------------------------------
/utils/generateEntries.js:
--------------------------------------------------------------------------------
1 | const glob = require('fast-glob');
2 | const path = require('path');
3 |
4 | module.exports = function generateEntries(config, extension = 'js', filenamePattern = config.general.sourceKey, cwd = config.general.sourcesPath) {
5 | const sourcePattern = `**/*.${filenamePattern}.${extension}`;
6 | const sourcesFiles = glob.sync(sourcePattern, { cwd: cwd });
7 |
8 | // if is multiple entries
9 | if (config && config.general && config.general.multiple) {
10 | const sources = {};
11 |
12 | sourcesFiles.forEach((file) => {
13 | const dir = path.dirname(file);
14 | const fileName = path.basename(file);
15 | const destFileName = fileName.replace(filenamePattern, config.general.bundleKey);
16 | const destFile = path.join(dir, destFileName);
17 | sources[destFile] = path.join(cwd, file);
18 | });
19 |
20 | return sources;
21 | }
22 |
23 | return sourcesFiles;
24 | };
25 |
--------------------------------------------------------------------------------
/utils/checkChunk.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 | const checkChunk = require("./checkChunk");
3 |
4 | const vendor1 = './node_modules/mkFullPathSync/babel';
5 | const vendor2 = './mkFullPathSync/node_modules/core-js';
6 | const regular = './regular/test'
7 | const excluded = ['babel', 'core-js']
8 |
9 |
10 | describe('Test utils/checkChunk.js', () => {
11 |
12 | it(`Should return false if a module comes empty`, () => {
13 | expect(checkChunk('', [])).toBe(false);
14 | });
15 |
16 | it(`Should detect if a module is at included`, () => {
17 | expect(checkChunk(vendor1, [], ['node_modules'])).toBe(true);
18 | expect(checkChunk(vendor2, [], ['node_modules'])).toBe(true);
19 | });
20 |
21 | it(`Should detect if a module comes from included but is excluded`, () => {
22 | expect(checkChunk(vendor1, [], ['node_modules'])).toBe(true);
23 | expect(checkChunk(vendor1, excluded, ['node_modules'])).toBe(false);
24 | });
25 |
26 | it(`Should return false if is not on included `, () => {
27 | expect(checkChunk(regular, [excluded], ['node_modules'])).toBe(false);
28 | });
29 | });
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const babel = require('./babel.config');
2 | const clientlibs = require('./clientlibs.config');
3 | const eslint = require('./eslint.config');
4 | const general = require('./general.config.js');
5 | const optimization = require('./optimization.config');
6 | const output = require('./output.config');
7 | const plugins = require('./plugins.config');
8 | const postcss = require('./postcss.config');
9 | const resolve = require('./resolve.config');
10 | const sass = require('./sass.config');
11 | const stylelint = require('./stylelint.config');
12 | const templates = require('./templates.config');
13 | const stats = require('./stats.config');
14 | const cache = require('./cache.config');
15 | const devServer = require('./devserver.config');
16 | const performance = require('./performance.config');
17 |
18 | const modules = general.modules;
19 |
20 | module.exports = {
21 | general,
22 | output,
23 | optimization,
24 | plugins,
25 | eslint,
26 | babel,
27 | modules,
28 | sass,
29 | clientlibs,
30 | stylelint,
31 | resolve,
32 | postcss,
33 | templates,
34 | stats,
35 | cache,
36 | devServer,
37 | performance
38 | };
39 |
--------------------------------------------------------------------------------
/utils/runStylelint.js:
--------------------------------------------------------------------------------
1 | const stylelint = require('stylelint');
2 | const { log } = require('./log');
3 | const linterError = require('./linterError');
4 |
5 | module.exports = function runStylelint(files, projectConfig, cb) {
6 | // extract from config
7 | const { failOnError } = projectConfig.stylelint;
8 | const { rootPath } = projectConfig.general;
9 |
10 | if (projectConfig.general.disableStyleLint) {
11 | return cb();
12 | }
13 |
14 | stylelint.lint({
15 | files,
16 | configBasedir: rootPath,
17 | quietDeprecationWarnings: true
18 | }).then((data) => {
19 | if (!data.errored) return cb();
20 |
21 | const fileError = JSON.parse(data.output);
22 |
23 | // show errors
24 | fileError
25 | .filter(errors => errors.warnings.length > 0)
26 | .map(error => error.warnings.forEach(er => linterError(er, error)));
27 |
28 | if (failOnError) {
29 | process.exit(1);
30 | }
31 |
32 | return fileError;
33 | }).catch(({ code, message }) => {
34 | log(__filename, 'error', message, 'error');
35 | // If config file not provided, continue
36 | if (code === 78) {
37 | cb();
38 | }
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/utils/getArgumentValue.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 |
3 | const getArgumentValue = require("./getArgumentValue");
4 |
5 | describe('Test utils/getArgumentValue.js', () => {
6 |
7 | it('Should return false, if argument object is empty or missing', () => {
8 | const lastArgs = [...process.argv];
9 | process.argv = false;
10 | const arg = getArgumentValue('--example=');
11 | expect(arg).toBe(false);
12 | process.argv = lastArgs;
13 | });
14 | it('Should return false if the argument is not passed', () => {
15 | const arg = getArgumentValue('--example');
16 | expect(arg).toBe(false);
17 | });
18 |
19 | it('Should return true if there a argument with no value', () => {
20 | const arg = getArgumentValue('--quiet');
21 | expect(arg).toBe(true);
22 | });
23 |
24 | it('Should return value, if there a argument with value', () => {
25 | const ArgValue = 'My nice passed arg string';
26 | const argName = '--example=';
27 | process.argv.push(`${argName}${ArgValue}`);
28 | const arg = getArgumentValue('--example=');
29 | expect(arg).toBe(ArgValue);
30 | });
31 | })
32 |
--------------------------------------------------------------------------------
/test/.febuild:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function (defaultConfig) {
4 | const rootPath = __dirname;
5 | const sourcesPath = path.join(rootPath, 'src');
6 | const common = path.join(sourcesPath, 'common');
7 | const includePaths = [path.join(common, 'styles'), defaultConfig.general.nodeModules];
8 |
9 | return {
10 | general: {
11 | projectKey: 'febuild',
12 | rootPath,
13 | sourcesPath,
14 | destinationPath: path.join(rootPath, 'dist'),
15 | common,
16 | sourceKey: 'src',
17 | bundleKey: 'dist'
18 | },
19 | sass: {
20 | includePaths
21 | },
22 | resolve: {
23 | alias: {
24 | common
25 | }
26 | },
27 | clientlibs: {
28 | override: true,
29 | skipCategories: ['febuild.author']
30 | },
31 | templates: {
32 | clientlibTemplate: (category, prefix) => `
33 | `
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ '**' ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | install:
11 |
12 | runs-on: ${{ matrix.os }}
13 |
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest, windows-latest]
17 | node-version: [18.x]
18 |
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v3
22 | - name: Setup Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - name: Install dependencies
27 | run: npm ci
28 | - name: Build Library
29 | run: npm run build --if-present
30 |
31 | test:
32 | runs-on: ${{ matrix.os }}
33 |
34 | strategy:
35 | matrix:
36 | os: [ubuntu-latest]
37 | node-version: [18.x]
38 |
39 | steps:
40 | - name: Checkout
41 | uses: actions/checkout@v2
42 | - name: Setup Node.js ${{ matrix.node-version }}
43 | uses: actions/setup-node@v3
44 | with:
45 | node-version: ${{ matrix.node-version }}
46 | - name: Install dependencies
47 | run: npm ci
48 | - name: test Library
49 | run: npm run test --if-present
50 |
51 |
--------------------------------------------------------------------------------
/test/.febuild.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function (defaultConfig) {
4 | const rootPath = __dirname;
5 | const sourcesPath = path.join(rootPath, 'src');
6 | const common = path.join(sourcesPath, 'common');
7 | const includePaths = [path.join(common, 'styles'), defaultConfig.general.nodeModules];
8 |
9 | return {
10 | general: {
11 | projectKey: 'febuild',
12 | rootPath,
13 | sourcesPath,
14 | destinationPath: path.join(rootPath, 'dist'),
15 | common,
16 | sourceKey: 'src',
17 | bundleKey: 'dist'
18 | },
19 | sass: {
20 | includePaths
21 | },
22 | resolve: {
23 | alias: {
24 | common
25 | }
26 | },
27 | clientlibs: {
28 | override: true,
29 | skipCategories: ['febuild.author']
30 | },
31 | templates: {
32 | clientlibTemplate: (category, prefix) => `
33 | `
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/config/optimization.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { isProduction, excludedFromVendors, bundleKey } = require('./general.config');
3 | const checkChunk = require('../utils/checkChunk');
4 | const nodeModules = `node_modules`;
5 |
6 | // curry to pass the module to check
7 | const test = ( excludes, includes) => (mod) => checkChunk(mod.context, excludes, includes);
8 |
9 | module.exports = {
10 | minimize: isProduction,
11 | usedExports: 'global',
12 | runtimeChunk: {
13 | name: `commons/treeshaking.${bundleKey}.js`
14 | },
15 | splitChunks: {
16 | chunks: 'initial',
17 | minChunks: 2,
18 | cacheGroups: {
19 | // Treeshake vendors in node_modules (but keep unique vendors at the clientlibs it belongs)
20 | vendors: {
21 | test: test([nodeModules], excludedFromVendors),
22 | minChunks: 2,
23 | enforce : true,
24 | name: `commons/vendors.${bundleKey}.js`,
25 | // used on at least 2 modules
26 | },
27 | // Treeshakes common imports, if used in more than 2 clientlibs
28 | treeshaking: {
29 | test: test([nodeModules]),
30 | minChunks: 2,
31 | enforce : true,
32 | name: `commons/treeshaking.${bundleKey}.js`,
33 | }
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release:
10 | name: Test, Build and Release
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os: [ ubuntu-latest ]
15 | node-version: [ 18.x ]
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v3
20 | - name: Setup Node.js
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ matrix['node-version'] }}
24 | - name: Install dependencies
25 | run: npm ci
26 | - name: Build Library
27 | run: npm run build --if-present
28 | - name: Run Tests
29 | run: npm test --if-present
30 | - name: Release
31 | uses: cycjimmy/semantic-release-action@v3
32 | with:
33 | dry_run: false
34 | extra_plugins: |
35 | @semantic-release/changelog
36 | @semantic-release/git
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40 | GIT_AUTHOR_NAME: github-actions
41 | GIT_AUTHOR_EMAIL: github-actions@github.com
42 | GIT_COMMITTER_NAME: github-actions
43 | GIT_COMMITTER_EMAIL: github-actions@github.com
44 | CI: true
45 |
--------------------------------------------------------------------------------
/utils/merge.test.js:
--------------------------------------------------------------------------------
1 | const merge = require("./merge");
2 |
3 | let inicial = {};
4 | let final = {};
5 | let merged = {};
6 |
7 | beforeEach(() => {
8 | inicial = {
9 | prop1: {
10 | prop1: {
11 | prop1: 'value',
12 | prop2: [1,2,4]
13 | }
14 | },
15 | prop2: {
16 | prop1: {
17 | prop1: 'value',
18 | }
19 | }
20 | };
21 | final = {
22 | prop1: {
23 | prop1: {
24 | prop1: true,
25 | }
26 | },
27 | };
28 |
29 | merged = merge(inicial,final);
30 | })
31 |
32 | describe('Test utils/merge.js', () => {
33 |
34 | it(`With 2 level deep, prop is overriden by merge`, () => {
35 | expect(merged.prop1.prop1.prop1).toBe(final.prop1.prop1.prop1);
36 | });
37 |
38 | it(`With if 2 level deep, inicial prop is ketp by merge`, () => {
39 | expect(merged.prop1.prop1.prop2).toBe(inicial.prop1.prop1.prop2);
40 | });
41 |
42 | it(`With if 2 level deep, new prop is filled by merge`, () => {
43 | expect(merged.prop1.prop1.prop3).toBe(final.prop1.prop1.prop3);
44 | });
45 |
46 | it(`Check if other levels are intact`, () => {
47 | expect(merged.prop2.prop1.prop1).toBe(inicial.prop2.prop1.prop1);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '20 12 * * 0'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'javascript' ]
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@v3
35 |
36 | # Initializes the CodeQL tools for scanning.
37 | - name: Initialize CodeQL
38 | uses: github/codeql-action/init@v2
39 | with:
40 | languages: ${{ matrix.language }}
41 |
42 | - run: npm run build --if-present
43 |
44 | - name: Perform CodeQL Analysis
45 | uses: github/codeql-action/analyze@v2
46 |
--------------------------------------------------------------------------------
/tasks/styles.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const styles = require('./styles');
4 | const defaults = require('../config');
5 | const extendConfig = require('../utils/extendConfig');
6 | const generateEntries = require('../utils/generateEntries');
7 |
8 | let config = extendConfig('./test/.febuild', defaults);
9 | let entries = {
10 | ...generateEntries(config, 'scss')
11 | };
12 | const { destinationPath, projectKey } = config.general;
13 |
14 | beforeAll(async () =>
15 | await new Promise(async (r) => {
16 | await styles(config);
17 | r();
18 | })
19 | );
20 |
21 | describe('Test task/styles.js', () => {
22 | Object.keys(entries).forEach((entry) => {
23 | const file = path.join(destinationPath, entry);
24 | const source = entries[entry];
25 | const ext = path.extname(file) === '.js' ? 'js' : 'css';
26 | const fileName = `${file.split('.').slice(0, -1).join('.')}.${ext}`;
27 | it(`Compile ${source} file and save ${entry} at destination folder`, async () => {
28 | const bundleContent = fs.readFileSync(fileName, { encoding:'utf8', flag:'r' });
29 | const sourceContent = fs.readFileSync(source, { encoding:'utf8', flag:'r' });
30 | expect(bundleContent).not.toBe(sourceContent);
31 | });
32 | })
33 | });
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tasks/clientlibs.js:
--------------------------------------------------------------------------------
1 | const { log } = require('../utils/log');
2 | const generateEntries = require('../utils/generateEntries');
3 | const getClientlib = require('../utils/getClientlib');
4 | const renderClientLibs = require('../utils/renderClientLibs');
5 |
6 | // extend log to proper say what file is running
7 | module.exports = (config) => {
8 | log(__filename, 'clientlibs task running...', '', 'info');
9 |
10 | const { extraEntries } = config.postcss;
11 |
12 | // checking all entries at this configuration
13 | let entries = {
14 | ...generateEntries(config),
15 | ...generateEntries(config, 'scss'),
16 | };
17 |
18 | entries = {
19 | ...entries,
20 | ...extraEntries ? {
21 | ...generateEntries(config, extraEntries.extension, extraEntries.filenamePattern, extraEntries.cwd),
22 | } : null,
23 | }
24 |
25 | // clientlibs to render
26 | const clientLibs = {};
27 |
28 | // get parse to check if it has css or js or both.
29 | Object.keys(entries).forEach((entryKey) => {
30 | const { name, folder, fileName, extension } = getClientlib(entryKey);
31 |
32 | if (!clientLibs[folder]) {
33 | clientLibs[folder] = { name, folder };
34 | }
35 |
36 | // set the extension
37 | clientLibs[folder][extension] = fileName;
38 | });
39 |
40 | Object.keys(clientLibs).forEach(lib => renderClientLibs(clientLibs[lib], config));
41 | };
42 |
--------------------------------------------------------------------------------
/utils/taskVerification.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { log, color } = require('./log');
3 |
4 | module.exports = (configuration, taskFolder = '../tasks/') => {
5 | log(__filename, `Tasks started with mode - ${color('green', configuration.general.mode)}`);
6 |
7 | if (configuration && configuration.general && configuration.general.task) {
8 | try {
9 | /* eslint-disable */
10 | // Exception to dinamic require of tasks
11 | const execute = require(`${taskFolder}${configuration.general.task}`);
12 |
13 | /* eslint-enable */
14 | execute(configuration);
15 | } catch (e) {
16 | log(__filename, e.message, '', 'info');
17 |
18 | try {
19 | /* eslint-disable */
20 | // try as a abs path script (external tasks) project scripts and share config
21 | const execute = require(path.resolve(`${configuration.general.task}`));
22 |
23 | /* eslint-enable */
24 | execute(configuration);
25 | } catch (error) {
26 | log(__filename, `Cannot find task to execute ${configuration.general.task}`, '', 'error');
27 | }
28 | }
29 | } else {
30 | // run all default default async
31 | configuration.general.defaultTasks.forEach((taskName) => {
32 | /* eslint-disable */
33 | const task = require(`${taskFolder}${taskName}`);
34 | /* eslint-enable */
35 | setTimeout(() => task(configuration));
36 | });
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | ## Description
7 |
8 |
9 |
10 | ## Related Issue
11 |
12 |
15 |
19 |
20 | Fixes #
21 |
22 | ## Types of changes
23 |
24 |
25 |
26 | - [ ] Bug fix (non-breaking change which fixes an issue)
27 | - [ ] New feature (non-breaking change which adds functionality)
28 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
29 |
30 | ## Checklist:
31 |
32 |
33 |
34 | - [ ] My code follows the code style of this project.
35 | - [ ] I have read the **[CONTRIBUTING](docs/CONTRIBUTING.md)** document.
36 | - [ ] All new and existing tests passed.
37 |
--------------------------------------------------------------------------------
/tasks/webpack.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const webpackTask = require('./webpack');
4 | const defaults = require('../config');
5 | const extendConfig = require('../utils/extendConfig');
6 | const generateEntries = require('../utils/generateEntries');
7 |
8 | let config = extendConfig('./test/.febuild', defaults);
9 | let entries = {
10 | ...generateEntries(config, 'js')
11 | };
12 | const { destinationPath, projectKey } = config.general;
13 |
14 | beforeAll(async () => {
15 | return await new Promise(async (resolve, reject) => {
16 | try {
17 | await webpackTask(config);
18 | resolve();
19 | } catch (e) {
20 | reject(e);
21 | }
22 | });
23 | }, 20000);
24 |
25 | describe('Test task/webpack.js', () => {
26 | Object.keys(entries).forEach((entry) => {
27 | const file = path.join(destinationPath, entry);
28 | const source = entries[entry];
29 | const ext = path.extname(file) === '.js' ? 'js' : 'css';
30 | const fileName = `${file.split('.').slice(0, -1).join('.')}.${ext}`;
31 | it(`Compile ${source} file and save ${entry} at destination folder`, async () => {
32 | const bundleContent = fs.readFileSync(fileName, { encoding:'utf8', flag:'r' });
33 | const sourceContent = fs.readFileSync(source, { encoding:'utf8', flag:'r' });
34 | expect(bundleContent).not.toBe(sourceContent);
35 | });
36 | })
37 | });
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/utils/extendConfig.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { log } = require('./log');
3 | const merge = require('./merge');
4 |
5 | module.exports = (configPath, config) => {
6 | const dir = path.join(config.general.rootPath, configPath);
7 |
8 | /* eslint-disable */
9 | // usually this is not a good options to have dynamic require, here is a exception
10 | const buildConfig = require(dir);
11 | const override = typeof buildConfig === 'function' ? buildConfig(config) : buildConfig;
12 |
13 | /* eslint-enable */
14 | // extending the configs
15 | log(__filename, `Extending Fe build config for ${path.dirname(configPath)}`);
16 |
17 | // this allows our .febuild create its own config per folder
18 | // if sourcesPath is not provided, .febuild file location is used
19 | const { general = {} } = override;
20 | let basePath = general.sourcesPath;
21 |
22 | if (!general.sourcesPath) {
23 | override.general = override.general || {};
24 | override.general.sourcesPath = path.dirname(dir);
25 | basePath = override.general.sourcesPath.split(config.general.rootPath)[1];
26 | }
27 |
28 | if (!general.destinationPath) {
29 | const parts = basePath.split(path.sep);
30 | parts[1] = 'dist';
31 | const dest = path.join(config.general.rootPath, ...parts);
32 | override.general = override.general || {};
33 | override.general.destinationPath = dest;
34 | }
35 |
36 | // config merge
37 | const copyConfig = merge({}, config);
38 | const extendedConfig = merge(copyConfig, override);
39 | return extendedConfig;
40 | };
41 |
--------------------------------------------------------------------------------
/utils/renderPostcss.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss');
2 | const { log } = require('./log');
3 | const getPlugins = require('./getPlugins');
4 |
5 | module.exports = function renderPostcss(input, outFile, config, cb) {
6 | const { plugins, failOnError } = config.postcss;
7 |
8 | try {
9 | // try to dynamic load of postcss plugins
10 | const runPlugins = getPlugins(plugins);
11 | const map = config.general.isProduction ? false : { inline: true, prev: input.sourceMap };
12 |
13 | // run postcss plugins at sass output
14 | postcss(runPlugins).process(input.css.toString(), { from: outFile, map })
15 | .then((result) => {
16 | // check all warnings
17 | const warns = result.warnings();
18 |
19 | if (warns && warns.length > 0) {
20 | // log warnings
21 | return result.warnings().forEach((warn) => {
22 | log(__filename, 'error', warn.toString(), 'error');
23 | // exit if fail on error is defined
24 | if (failOnError) {
25 | process.exit(1);
26 | }
27 | });
28 | }
29 |
30 | // success and return
31 | log(__filename,
32 | `PostCSS applied: ${plugins.map(ent => (Array.isArray(ent) ? ent[0] : ent)).join(', ')}`,
33 | input.destFile,
34 | 'success');
35 |
36 | return cb(result);
37 | });
38 | } catch (error) {
39 | log(__filename, 'Postcss error', error.message, 'error');
40 | // exit if fail on error is defined
41 | if (failOnError) {
42 | process.exit(1);
43 | }
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/tasks/run.js:
--------------------------------------------------------------------------------
1 | const glob = require('fast-glob');
2 | const config = require('../config');
3 | const extendConfig = require('../utils/extendConfig');
4 | const taskVerification = require('../utils/taskVerification');
5 | const { log } = require('../utils/log');
6 |
7 | // start log messages
8 | log(__filename, ' Looking for configuration files', ', if it finds its runs ', 'info');
9 |
10 | // check if there is a params sent to run a single config file
11 | if (config && config.general && config.general.configFile) {
12 | log(__filename, ' Running configuration from parameter');
13 |
14 | // load the configuration
15 | const configuration = extendConfig(config.general.configFile, config);
16 |
17 | // run the proper task on the sent config
18 | taskVerification(configuration);
19 | } else {
20 | // No configFile set then lookup for configurations files
21 | const configPattern = `**/${config.general.extendConfigurations}`;
22 | const availableBuilds = glob.sync([configPattern, `${configPattern}.js`], { cwd: config.general.rootPath, ignore: ['./node_modules/**'] });
23 |
24 | // log what is happening
25 | if (availableBuilds.length === 0) {
26 | log(__filename, ' No configuration files found, running default configuration');
27 |
28 | taskVerification(config);
29 | } else {
30 | log(__filename, ' Found local configurations', ' - Running them instead', 'warning');
31 |
32 | // extending every found build options
33 | availableBuilds.forEach((configPath) => {
34 | const configuration = extendConfig(configPath, config);
35 |
36 | // check if a specific task was sent
37 | taskVerification(configuration);
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/utils/renderClientLibs.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const { log, color } = require('./log');
4 | const writeFile = require('./writeFile');
5 | const mkFullPathSync = require('./mkFullPathSync');
6 |
7 | module.exports = function renderClientLibs(clientLibObject, config) {
8 | // extract from config
9 | const { projectKey, destinationPath } = config.general;
10 | const { name, folder, js, scss } = clientLibObject;
11 | const { override } = config.clientlibs;
12 | const { clientlibTemplate } = config.templates;
13 | const absolutePath = path.join(destinationPath, folder);
14 |
15 | log(__filename, `checking ${color('cyan', `${projectKey}.${name}`)}`);
16 |
17 | // check if the folder already exist
18 | if (!fs.existsSync(absolutePath)) {
19 | log(__filename, `Folder for ${name} does not exist creating ${folder}`);
20 |
21 | mkFullPathSync(absolutePath);
22 |
23 | log(__filename, `Creating ${color('cyan', `${projectKey}.${name}`)}`);
24 | }
25 |
26 | // if there is a scss or js we write the css.txt
27 | // write css.txt
28 | if (scss) {
29 | writeFile(path.join(absolutePath, 'css.txt'), `${scss.replace('.scss', '.css')}`, override);
30 | }
31 |
32 | // write css.txt
33 | if (js) {
34 | writeFile(path.join(absolutePath, 'js.txt'), `${js}`, override);
35 | }
36 |
37 | const extensionFile = config.postcss.extraEntries?.extension;
38 | const hasExtraEntries = extensionFile && clientLibObject[extensionFile];
39 | const fileName = clientLibObject[extensionFile];
40 | if ( hasExtraEntries ) {
41 | writeFile(path.join(absolutePath, `${extensionFile}.txt`), `${fileName}`, override);
42 | }
43 |
44 | // write .content.xml
45 | const content = clientlibTemplate(name, projectKey);
46 | writeFile(path.join(absolutePath, '.content.xml'), content, override);
47 | };
--------------------------------------------------------------------------------
/utils/getPlugins.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 |
3 | describe('Test getPlugins.js', () => {
4 | beforeEach(() => {
5 | jest.resetModules();
6 | });
7 |
8 | it('Should load a plugin from string', () => {
9 | jest.mock('postcss-fe-build-test-a', () => () => 'mocked-plugin-a', { virtual: true });
10 |
11 | const getPlugins = require('./getPlugins');
12 | const plugins = ['postcss-fe-build-test-a'];
13 | const result = getPlugins(plugins);
14 |
15 | expect(result).toHaveLength(1);
16 | expect(result[0]()).toBe('mocked-plugin-a');
17 | });
18 |
19 | it('Should load a plugin from [plugin, options] format', () => {
20 | jest.mock('postcss-fe-build-test-b', () => ({
21 | default: (opts) => `mocked-plugin-b:${JSON.stringify(opts)}`
22 | }), { virtual: true });
23 |
24 | const getPlugins = require('./getPlugins');
25 | const plugins = [['postcss-fe-build-test-b', { extractAll: false }]];
26 | const result = getPlugins(plugins);
27 |
28 | expect(result).toHaveLength(1);
29 | expect(result[0]).toBe('mocked-plugin-b:{"extractAll":false}');
30 | });
31 |
32 |
33 | it('Should ignore falsy values in plugin list', () => {
34 | jest.mock('postcss-fe-build-test-c', () => () => 'mocked-plugin-a', { virtual: true });
35 |
36 | const getPlugins = require('./getPlugins');
37 | const plugins = [null, undefined, false, 'postcss-fe-build-test-c'];
38 | const result = getPlugins(plugins);
39 |
40 | expect(result).toHaveLength(1);
41 | expect(result[0]()).toBe('mocked-plugin-a');
42 | });
43 |
44 | it('Should throw on invalid plugin entry', () => {
45 | const getPlugins = require('./getPlugins');
46 |
47 | const invalidPlugins = [{ foo: 'bar' }, 123, true];
48 | invalidPlugins.forEach(plugin => {
49 | expect(() => getPlugins([plugin])).toThrow('Invalid plugin entry');
50 | });
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/utils/generateEntries.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 |
3 | const defaults = require('../config');
4 | const extendConfig = require('./extendConfig');
5 | const generateEntries = require('./generateEntries');
6 |
7 | const config = extendConfig('./test/.febuild', defaults);
8 |
9 | describe('Test utils/generateEntries.js', () => {
10 | it('Should throw an error if there is no config', () => {
11 | expect(generateEntries).toThrowError();
12 | });
13 |
14 | it('Should find no entries if there is no files with txt extension', () => {
15 | const entries = generateEntries(config, 'txt');
16 | expect(Object.keys(entries).length).toBe(0);
17 | });
18 |
19 | it('Should find 2 javascripts entries at ./test', () => {
20 | const entries = generateEntries(config);
21 | expect(Object.keys(entries).length).toBe(2);
22 | });
23 |
24 | it('Should find 3 SCSS entries at ./test', () => {
25 | const entries = generateEntries(config,'scss');
26 | expect(Object.keys(entries).length).toBe(3);
27 |
28 | });
29 |
30 | it(`Destination file should be based on same name pattern as source file`, () => {
31 | const entries = generateEntries(config);
32 | const passed = Object.keys(entries).reduce((pass, key) => {
33 | const value = entries[key];
34 | const destination = key.replace(`.${config.general.bundleKey}`, '');
35 | const source = value.replace(`.${config.general.sourceKey}`,'');
36 | return pass && source.indexOf(destination) >= 0;
37 | }, true)
38 | expect(passed).toBe(true);
39 | });
40 |
41 | it('Generate file list for single bundle build at ./test, eg [file1,file2]', () => {
42 | config.general.multiple = false;
43 | const entries = generateEntries(config);
44 | expect(Array.isArray(entries)).toBe(true);
45 | });
46 | })
--------------------------------------------------------------------------------
/docs/tasks.md:
--------------------------------------------------------------------------------
1 | # Available NPM Tasks
2 |
3 | The following npm tasks are available by default after installing fe-build, but you need to add them manually to your package.json file.
4 |
5 | ## Compile JavaScript/ECMAScript
6 |
7 | Add `nc-fe-build --task=webpack` task in package.json scripts
8 |
9 | ```json
10 | "scripts": {
11 | "build:js": "nc-fe-build --task=webpack"
12 | },
13 | ```
14 |
15 | To execute it, open the terminal and run `npm run build:js` from the same folder where the file package.json is.
16 |
17 | ## Compile Sass
18 |
19 | ```json
20 | "scripts": {
21 | "build:css": "nc-fe-build --task=styles"
22 | },
23 | ```
24 |
25 | ## Create ClientLibrary Files
26 |
27 | ```json
28 | "scripts": {
29 | "build:clientlibs": "nc-fe-build --task=clientlibs"
30 | }
31 | ```
32 |
33 | ## Run an Specific Config File
34 |
35 | You can run the build from a single configuration file:
36 |
37 | ```json
38 | "scripts": {
39 | "build:config": "nc-fe-build --config-file=path/to/configFile"
40 | }
41 | ```
42 |
43 | ## Watch Sass and JS
44 |
45 | ```json
46 | "scripts": {
47 | "watch:js": "nc-fe-build --task=webpack --watch",
48 | "watch:css": "nc-fe-build --task=styles --watch",
49 | }
50 | ```
51 |
52 | ## Analyze JS
53 |
54 | Analyze the bundles with [Webpack Bundle Analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer).
55 |
56 | ```json
57 | "scripts": {
58 | "analyze": "nc-fe-build --task=webpack --analyze --development"
59 | }
60 | ```
61 |
62 | To change the port number, you can append the argument `--port=` followed by a valid port number.
63 |
64 | # Additional Arguments
65 | You can add the following arguments to the `nc-fe-build` command in order to change its behavior.
66 |
67 | | Argument | Description |
68 | | --------- | ----------- |
69 | | `--quiet` | Displays less messages in the output |
70 | | `--disable-styelint` | Disables Stylelint |
71 |
--------------------------------------------------------------------------------
/tasks/clientlibs.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const clientlibTask = require('./clientlibs');
5 | const defaults = require('../config');
6 | const extendConfig = require('../utils/extendConfig');
7 | const generateEntries = require('../utils/generateEntries');
8 | const getClientlib = require('../utils/getClientlib');
9 |
10 | let config = extendConfig('./test/.febuild', defaults);
11 | let entries = {
12 | ...generateEntries(config),
13 | ...generateEntries(config, 'scss')
14 | };
15 | const { destinationPath, projectKey } = config.general;
16 | const { clientlibTemplate } = config.templates;
17 | // clear
18 | beforeAll(async () => {
19 | await clientlibTask(config);
20 | });
21 |
22 | describe('Test task/clientlibs.js', () => {
23 | Object.keys(entries).forEach((entry) => {
24 | const file = path.join(destinationPath, entry);
25 | const type = path.extname(file);
26 | const dir = path.dirname(file);
27 | const { name, fileName } = getClientlib(entry);
28 | const ext = type == '.js' ? 'js' : 'css';
29 | const txtContet = `${fileName.split('.').slice(0, -1).join('.')}.${ext}`;
30 | const txtPath = path.join(dir, `${ext}.txt`);
31 |
32 | it(`TXT files should be created and point to ${entry} file`, () => {
33 | const fileContent = fs.readFileSync(txtPath, { encoding:'utf8', flag:'r' });
34 | expect(fileContent).toBe(txtContet);
35 | });
36 |
37 | it(`Should create .content.xml files with it's category "${name}" based on template`, () => {
38 | const template = clientlibTemplate(name, projectKey);
39 | const fileContent = fs.readFileSync(path.join(dir,'.content.xml'), { encoding:'utf8', flag:'r' });
40 | expect(fileContent).toBe(template);
41 | });
42 | })
43 | });
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/utils/extendConfig.test.js:
--------------------------------------------------------------------------------
1 | process.argv.push('--quiet');
2 |
3 | const json = require('./../test/.febuild');
4 | const minimal = require('./../test/src/minimal/.febuild');
5 | const config = require('../config');
6 | const extendConfig = require('./extendConfig');
7 | const finalValues = json(config);
8 |
9 |
10 | describe('Test utils/extendConfig.js', () => {
11 | it('Should throw an error no .febuild file is found ', () => {
12 | expect(() => extendConfig('.febuild', config)).toThrowError();
13 | });
14 |
15 | it('Should find a .febuild file, read and merge into defaults values', () => {
16 | const newConfig = extendConfig('./test/.febuild', config);
17 | const reducer = (o,f) => {
18 | return Object.keys(o).reduce((v,k) => {
19 | if (typeof o[k] === 'function') return v && true;
20 | if (typeof o[k] !== 'object' || Array.isArray(o[k])) return v && JSON.stringify(f[k]) === JSON.stringify(o[k]);
21 | return v && reducer(o[k],f[k]);
22 | }, true);
23 | }
24 | const test = reducer(finalValues, newConfig);
25 | expect(test).toBe(true);
26 | });
27 |
28 | it('should work even if no destination path or source paths are configured', () => {
29 | delete config.general.destinationPath;
30 | delete config.general.sourcesPath;
31 | const newConfig = extendConfig('./test/src/minimal/.febuild', config);
32 | const reducer = (o,f) => {
33 | return Object.keys(o).reduce((v,k) => {
34 | if (typeof o[k] === 'function') return v && true;
35 | if (typeof o[k] !== 'object' || Array.isArray(o[k])) {
36 | return v && JSON.stringify(f[k]) === JSON.stringify(o[k]);
37 | }
38 | return v && reducer(o[k],f[k]);
39 | }, true);
40 | }
41 | const test = reducer(minimal, newConfig);
42 | expect(test).toBe(true);
43 | });
44 | });
--------------------------------------------------------------------------------
/tasks/styles.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { log } = require('../utils/log');
3 | const generateEntries = require('../utils/generateEntries');
4 | const renderStyles = require('../utils/renderStyles');
5 |
6 | // extend log to proper say what file is running
7 | module.exports = (config) => {
8 | return new Promise((resolve) => {
9 | if (config && config.general && config.general.watch) {
10 | try {
11 | log(__filename, 'Watcher Sass / autoprefixer running...', '', 'info');
12 |
13 | const gaze = require('gaze');
14 | const sassPattern = path.join(config.general.sourcesPath, `**/*.${config.general.sourceKey}.scss`);
15 |
16 | gaze(sassPattern, function () {
17 | // simple debounce with timeout for only save the last if several events are triggered
18 | this.on('all', (event, file) => {
19 | // trigger a save
20 | const relativePath = path.relative(config.general.sourcesPath, path.dirname(file));
21 | const fileName = path.basename(file)
22 | .replace(config.general.sourceKey, config.general.bundleKey);
23 | const destFile = path.join(relativePath, fileName);
24 |
25 | // override to keep alive
26 | config.stylelint.failOnError = false;
27 |
28 | renderStyles(file, destFile, config);
29 | });
30 | });
31 | } catch (e) {
32 | log(__filename, 'Something is missing, you need install dev dependencies for this.', e.message, 'error');
33 | }
34 | } else {
35 | log(__filename, 'Sass / autoprefixer running...', '', 'info');
36 |
37 | // checking all entries at this configuration
38 | const entries = generateEntries(config, 'scss');
39 | const promises = Object.keys(entries).map(file => renderStyles(entries[file], file, config));
40 | Promise.allSettled(promises).then((results) => {
41 | log(__filename, 'Styles done', '', 'info');
42 | resolve();
43 | });
44 | }
45 | });
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@netcentric/fe-build",
3 | "version": "5.3.1",
4 | "description": "Frontend build tools for AEM projects.",
5 | "license": "Apache-2.0",
6 | "author": "Cognizant Netcentric (https://www.netcentric.biz)",
7 | "private": false,
8 | "publishConfig": {
9 | "access": "public"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/Netcentric/fe-build.git "
14 | },
15 | "scripts": {
16 | "clean": "rm -rf ./test/dist",
17 | "test": "npm run clean && npm run jest",
18 | "jest": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest -u --runInBand"
19 | },
20 | "keywords": [
21 | "build"
22 | ],
23 | "files": [
24 | "/config",
25 | "/utils",
26 | "/tasks",
27 | "/cli",
28 | "/docs",
29 | "/hooks",
30 | "./README.md",
31 | "LICENSE"
32 | ],
33 | "bin": {
34 | "nc-fe-build": "./cli/index.js"
35 | },
36 | "paths": {
37 | "configs": "./config",
38 | "tasks": "./tasks"
39 | },
40 | "dependencies": {
41 | "@babel/core": "^7.26.7",
42 | "@babel/plugin-transform-runtime": "^7.25.9",
43 | "@babel/preset-env": "^7.26.7",
44 | "autoprefixer": "^10.4.20",
45 | "babel-loader": "9.1.2",
46 | "core-js": "^3.40.0",
47 | "eslint": "^8.57.1",
48 | "eslint-webpack-plugin": "^4.2.0",
49 | "fast-glob": "^3.3.3",
50 | "gaze": "^1.1.3",
51 | "jest": "^29.7.0",
52 | "postcss": "^8.5.1",
53 | "sass": "^1.83.4",
54 | "semver": "^7.6.3",
55 | "stylelint": "^16.14.1",
56 | "webpack": "^5.97.1"
57 | },
58 | "devDependencies": {
59 | "del-cli": "^6.0.0",
60 | "stylelint-config-standard-scss": "^14.0.0",
61 | "webpack-bundle-analyzer": "^4.10.2",
62 | "webpack-cli": "^6.0.1"
63 | },
64 | "overrides": {
65 | "semver": "^7.6.3"
66 | },
67 | "browserslist": [
68 | "last 2 versions",
69 | "not ie > 0",
70 | "not ie_mob > 0",
71 | "not dead",
72 | "not OperaMini all"
73 | ],
74 | "engines": {
75 | "node": ">=18.12.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/utils/log.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { rootPath, quiet } = require('../config/general.config');
3 |
4 | const colorOptions = {
5 | bgblack: '\x1b[40m',
6 | bgblue: '\x1b[44m',
7 | bgcyan: '\x1b[46m',
8 | bggreen: '\x1b[42m',
9 | bgmagenta: '\x1b[45m',
10 | bgred: '\x1b[41m',
11 | bgwhite: '\x1b[47m',
12 | bgyellow: '\x1b[43m',
13 | black: '\x1b[30m',
14 | blink: '\x1b[5m',
15 | blue: '\x1b[34m',
16 | bright: '\x1b[1m',
17 | cyan: '\x1b[36m',
18 | dim: '\x1b[2m',
19 | green: '\x1b[32m',
20 | hidden: '\x1b[8m',
21 | magenta: '\x1b[35m',
22 | red: '\x1b[31m',
23 | reset: '\x1b[0m',
24 | reverse: '\x1b[7m',
25 | underscore: '\x1b[4m',
26 | white: '\x1b[37m',
27 | yellow: '\x1b[33m'
28 | };
29 |
30 | const color = (c, str, bg = colorOptions.reset) => `${colorOptions[c]}${str}${bg}`;
31 |
32 | const emojis = {
33 | error: [' ! '],
34 | success: [' ✔ '],
35 | };
36 |
37 | const random = array => array[Math.floor(Math.random() * array.length)];
38 |
39 | const severity = {
40 | info: {
41 | icon: () => color('dim', '☞ '),
42 | color: 'dim'
43 | },
44 | warning: {
45 | icon: () => color('yellow', '⚠ '),
46 | color: 'yellow'
47 | },
48 | log: {
49 | icon: () => color('yellow', '⚠ '),
50 | color: 'white'
51 | },
52 | success: {
53 | icon: () => color('dim', `${(emojis.success)} -> `),
54 | color: 'green'
55 | },
56 | error: {
57 | icon: () => color('red', '✖ '),
58 | color: 'red'
59 | }
60 | };
61 |
62 | const log = (file = false, title, extra = '', sev = 'info', force = false) => {
63 | const fileRef = file ? `[${path.relative(rootPath, file)}] ` : '';
64 | const fun = sev === 'error' ? sev : 'log';
65 | if (!quiet || force) {
66 | const icon = severity[sev] ? severity[sev].icon() : '';
67 | console[fun](`${color('dim', fileRef)}${icon}${color(severity[sev].color, title)}${color('dim', extra)}`);
68 | }
69 | };
70 |
71 | module.exports = {
72 | colorOptions,
73 | color,
74 | emojis,
75 | random,
76 | severity,
77 | log
78 | };
79 |
--------------------------------------------------------------------------------
/utils/renderSass.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const sass = require('sass');
3 | const mkFullPathSync = require('./mkFullPathSync');
4 | const writeFile = require('./writeFile');
5 | const { log } = require('./log');
6 |
7 | module.exports = function renderSass(dest, file, config, cb, write = false) {
8 | // extract sass only configs
9 | const { outputStyle, includePaths, failOnError, adicionalOptions } = config.sass;
10 |
11 | // proper extension
12 | const destFile = dest.replace('.scss', '.css');
13 |
14 | // url of the saved file
15 | const outFile = path.join(config.general.destinationPath, destFile);
16 |
17 |
18 | // extract from config
19 | const compiled = sass.compileAsync(file, {
20 | outputStyle,
21 | loadPaths:includePaths,
22 | sourceMap: !config.general.isProduction,
23 | // adicional config from https://sass-lang.com/documentation/js-api/interfaces/options/
24 | ...adicionalOptions
25 | });
26 |
27 | compiled.then((result) => {
28 | // create folder if it does not exist
29 | mkFullPathSync(path.dirname(outFile));
30 |
31 | // write the css file, overriding it if write is enabled
32 | // its better to skip this so we only write the css once reducing I/O
33 | if (write) {
34 | writeFile(outFile, result.css, true);
35 | }
36 |
37 | // if is dev add source maps
38 | if (!config.general.isProduction && write) {
39 | writeFile(`${outFile}.map`, JSON.stringify(result.sourceMap), true);
40 | }
41 |
42 | // pass the destination relative for map
43 | result.destFile = destFile;
44 |
45 | // log and call back
46 | log(__filename, `Sass rendered - ${path.basename(destFile)}`, ` -- `, 'success');
47 | return cb(result, outFile);
48 |
49 | }).catch((error) => {
50 | log(__filename, `${destFile} ${error.message}!`, '', 'error');
51 | // if set to exit on error, you might not want to exit on all cases
52 | if (failOnError) {
53 | process.exit(1);
54 | } else {
55 | log(__filename, `Sass file not rendered - ${path.basename(destFile)}`, ``, 'error');
56 | // skip the rest
57 | return;
58 | }
59 | });
60 | };
61 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for choosing to contribute!
4 |
5 | The following are a set of guidelines to follow when contributing to this project.
6 |
7 | ## Code Of Conduct
8 |
9 | This project adheres to the [code of conduct](CODE_OF_CONDUCT.md). By participating,
10 | you are expected to uphold this code.
11 |
12 | ## Have A Question?
13 |
14 | Start by filing an issue. The existing committers on this project work to reach
15 | consensus around project direction and issue solutions within issue threads
16 | (when appropriate).
17 |
18 | ## getting started
19 | 1 - Update the git hooks for commit header and pre commit actions
20 |
21 | Start by running the follow commands to add hooks to git
22 | ```mkdir -p .git && mkdir -p .git/hooks && cp ./hooks/pre-commit ./.git/hooks/pre-commit```
23 |
24 | ## Automated Release
25 |
26 | ### Release
27 | - based on Angular Commit Message Conventions in commits -
28 | https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-header
29 | - Commit message format is used to build:
30 | * Release notes
31 | * Changelog updates
32 | * NPM package semver
33 |
34 | ### Commit message Convention
35 |
36 | ```
37 | ():
38 | │ │ │
39 | │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
40 | │ │
41 | │ └─⫸ Commit Scope (optional): config|utils|tasks
42 | │
43 | └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
44 | ```
45 |
46 | #### Major Version Release:
47 |
48 | In order to trigger Major Version upgrade, `BREAKING CHANGE:` needs to be in the footer of a commit message:
49 |
50 | ```
51 | ():
52 |
53 | BREAKING CHANGE:
54 | ```
55 |
56 | ## Code Reviews
57 |
58 | All submissions should come in the form of pull requests and need to be reviewed
59 | by project committers. Please always open a related issue before creating a pull Request.
60 | Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/)
61 | for more information on sending pull requests.
62 |
63 | Lastly, please follow the [pull request template](.github/PULL_REQUEST_TEMPLATE.md) when
64 | submitting a pull request!
65 |
--------------------------------------------------------------------------------
/tasks/webpack.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const ESLintPlugin = require('eslint-webpack-plugin');
3 |
4 | const { log } = require('../utils/log');
5 | const generateEntries = require('../utils/generateEntries');
6 | // extend log to proper say what file is running
7 | module.exports = (config) => {
8 | return new Promise((r) => {
9 | // checking all entries at this configuration
10 | const entry = generateEntries(config);
11 |
12 | // make sure destination path is the same config as output
13 | if (config && config.general && config.general.destinationPath) {
14 | config.output.path = config.general.destinationPath;
15 | }
16 |
17 | // setting rules for modules
18 | const module = {
19 | rules: config.general.modules.map(rule => config[rule])
20 | };
21 |
22 | // log at the beginning
23 | log(__filename, 'Webpack transpile running...', '', 'info');
24 |
25 | // extract from flatten configs to webpack
26 | const { output, plugins, optimization, resolve, externals, stats, performance, cache, devServer, eslint } = config;
27 | const { mode, watch, devtool } = config.general;
28 | // check if there is any entry
29 | plugins.push(new ESLintPlugin(eslint));
30 |
31 | if (entry && Object.keys(entry).length > 0) {
32 | // run webpack
33 | webpack({
34 | mode,
35 | watch,
36 | entry,
37 | output,
38 | module,
39 | plugins,
40 | optimization,
41 | devtool,
42 | resolve,
43 | performance,
44 | stats,
45 | cache,
46 | devServer,
47 | ...externals && { externals }
48 | }, (err, stats) => {
49 | // output the resulting stats.
50 | if (stats && stats.toString) {
51 | log(__filename, stats.toString({ colors: true }));
52 | }
53 |
54 | if (!watch && (stats && stats.hasErrors())) {
55 | log(__filename, stats.toString(), '', 'error');
56 | process.exit(1);
57 | }
58 |
59 | if (err) {
60 | log(__filename, err.toString(), '', 'error');
61 | process.exit(1);
62 | }
63 |
64 | // log completion
65 | log(__filename, 'Webpack transpile ended', '', 'success');
66 | r();
67 | });
68 | } else {
69 | log(__filename, 'No entries for webpack, nothing found', '', 'info');
70 | r();
71 | }
72 | });
73 | };
74 |
--------------------------------------------------------------------------------
/.github/workflows/manual-release.yml:
--------------------------------------------------------------------------------
1 | name: Manual Release
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | version:
6 | description: 'Version'
7 | type: choice
8 | required: true
9 | default: fix
10 | options:
11 | - fix
12 | - feat
13 | - BREAKING CHANGE
14 | dryRun:
15 | description: 'DryRun'
16 | type: boolean
17 | default: true
18 | # ENV and Config
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
22 | GIT_AUTHOR_NAME: github-actions
23 | GIT_AUTHOR_EMAIL: github-actions@github.com
24 | GIT_COMMITTER_NAME: github-actions
25 | GIT_COMMITTER_EMAIL: github-actions@github.com
26 | CI: true
27 | CONFIG_NODE_VERSION: '["18.x"]'
28 | CONFIG_OS: '["ubuntu-latest"]'
29 | # Main Job
30 | jobs:
31 | config:
32 | runs-on: ubuntu-latest
33 | outputs:
34 | NODE_VERSION: ${{ steps.set-config.outputs.CONFIG_NODE_VERSION }}
35 | OS: ${{ steps.set-config.outputs.CONFIG_OS }}
36 | steps:
37 | - id: set-config
38 | run: |
39 | echo "CONFIG_NODE_VERSION=${{ toJSON(env.CONFIG_NODE_VERSION) }}" >> $GITHUB_OUTPUT
40 | echo "CONFIG_OS=${{ toJSON(env.CONFIG_OS) }}" >> $GITHUB_OUTPUT
41 | release:
42 | name: Test, Build and force Release
43 | needs: config
44 |
45 | runs-on: ${{ matrix.OS }}
46 | strategy:
47 | matrix:
48 | OS: ${{ fromJSON(needs.config.outputs.OS) }}
49 | NODE_VERSION: ${{ fromJSON(needs.config.outputs.NODE_VERSION) }}
50 |
51 | steps:
52 | - name: Checkout repo
53 | uses: actions/checkout@v3
54 | - name: Setup Node.js ${{ matrix.NODE_VERSION }}
55 | uses: actions/setup-node@v3
56 | with:
57 | node-version: ${{ matrix.NODE_VERSION }}
58 | - name: Commit trigger
59 | run: |
60 | git commit --allow-empty -m "${{ github.event.inputs.version }}: Trigger Manual Release
61 |
62 | ${{ github.event.inputs.version }}:Forced Manual Release without code changes"
63 | - name: Install dependencies
64 | run: npm ci
65 | - name: Build Library
66 | run: npm run build --if-present
67 | - name: Run Tests
68 | run: npm test --if-present
69 | - name: Publish npm package
70 | uses: cycjimmy/semantic-release-action@v3
71 | with:
72 | dry_run: ${{ github.event.inputs.dryRun == 'true' }}
73 | extra_plugins: |
74 | @semantic-release/changelog
75 | @semantic-release/git
76 |
--------------------------------------------------------------------------------
/docs/quick_start.md:
--------------------------------------------------------------------------------
1 | # Quick start
2 |
3 | The first configurations that you need to adapt are probably the source and destination paths.
4 | The default paths are `src` for the source path and `dist` for the destination path. If you have a different structure, you can override these values in your `.febuild` file.
5 |
6 | e.g. In your project:
7 |
8 | ```
9 | -- package.json
10 | -- projectSrcDir
11 | |-- component
12 | |-- file.scss
13 | ```
14 |
15 | Note that on the first execution of the `npm run build` task, probably no files will be processed, because no file will match with default settings.
16 |
17 | To start the build and change the default settings, add a `.febuild` file one level up from where your `projectSrcDir` directory is with the following content:
18 |
19 | ```javascript
20 | module.exports = {}
21 | ```
22 |
23 | You can check the default settings for each specific section in the [configuration](./docs/configuration.md) documentation.
24 |
25 | ## Custom Source Path
26 |
27 | In order to start processing the files in your project, two updates are needed:
28 |
29 | 1. Add the `source` suffix to all the files that needs to be processed. This suffix value is defined in `general.sourceKey`.
30 | e.g.: `file.scss` --> `file.source.scss`
31 | 2. In the `.febuild` file, change the source directory `projectSrcDir` configuration to the path to where your source code is.
32 |
33 | ```javascript
34 | module.exports = {
35 | general: {
36 | sourcesPath: './projectSrcDir',
37 | }
38 | }
39 | ```
40 |
41 | if `sourcePath` is not provided, the path where the `.febuild` file is will be used instead. For this example it will be enough.
42 |
43 | After running again the `npm run build` task, this will be the output the destination folder:
44 |
45 | ```
46 | -- package.json
47 | -- .febuild
48 | -- projectSrcDir
49 | |-- component
50 | |-- file.source.scss
51 | -- dist
52 | |-- component
53 | |-- file.bundle.scss
54 | ```
55 |
56 | ## Custom Destination Path
57 |
58 | To add a custom destination path, add to the `.febuild` file the property `general.destinationPath` with the desired path.
59 |
60 | ```javascript
61 | module.exports = {
62 | general: {
63 | sourcesPath: './projectSrcDir',
64 | destinationPath: path.resolve(__dirname, '..', 'custom', 'dist', 'path')
65 | }
66 | }
67 | ```
68 |
69 | Then execute the `npm run build` task and check the results.
70 |
71 | ```
72 | -- package.json
73 | -- .febuild
74 | -- projectSrcDir
75 | |-- component
76 | |-- file.source.scss
77 | -- custom
78 | |-- dist
79 | |-- path
80 | |-- component
81 | |-- file.bundle.scss
82 | ```
83 |
84 | For more customizations, please check the [Configuration document](./configuration.md).
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @netcentric/fe-build
2 |
3 | Frontend build tools for AEM projects.
4 |
5 | [](https://npmjs.org/package/@netcentric/fe-build)
6 | [](https://github.com/netcentric/fe-build/actions)
7 | [](https://github.com/netcentric/fe-build/actions)
8 | [](https://github.com/semantic-release/semantic-release)
9 | [](https://opensource.org/licenses/Apache-2.0)
10 |
11 | ## Abstract
12 | All-in-one solution for modern Frontend projects, with special focus on [Adobe Experience Manager](https://business.adobe.com/products/experience-manager/adobe-experience-manager.html) development (AEM). It compiles your Scss and JS source files and creates the appropriate [clientLibs](https://experienceleague.adobe.com/docs/experience-manager-65/developing/introduction/clientlibs.html?lang=en) to be included by AEM.
13 |
14 | ## Getting started
15 | Use [npm](https://docs.npmjs.com/about-npm/) to install fe-build:
16 | ```
17 | npm i @netcentric/fe-build
18 | ```
19 |
20 | Add the `nc-fe-build` task in the scripts section of your package.json file:
21 | ```json
22 | "scripts": {
23 | "build": "nc-fe-build"
24 | },
25 | ```
26 |
27 | Create a directory named `src` and put some Scss and JS files there named "*.source.scss" and "*.source.js".
28 |
29 | Create a file named `.febuild` file one level up from where the source code directory is with the following content:
30 | ```javascript
31 | module.exports = {}
32 | ```
33 |
34 | Finally, run the build task:
35 | ```bash
36 | npm run build
37 | ```
38 |
39 | ## Features
40 | ### JavaScript
41 |
42 | - Lint source code with [ESLint](https://eslint.org/).
43 | - Transpilation with [Babel](https://babeljs.io/).
44 | - [core-js](https://github.com/zloirock/core-js) polyfills are automatically added when needed.
45 | - Bundled and optimized with [Webpack](https://webpack.js.org/).
46 | - Analyze bundles with [Webpack Bundle Analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer).
47 |
48 | ### CSS
49 |
50 | - Lint source code with [Stylelint](https://stylelint.io/).
51 | - [SASS](https://sass-lang.com/) compilation.
52 | - Add vendor prefixes with [Autoprefixer](https://github.com/postcss/autoprefixer).
53 |
54 | ### ClientLibraries
55 |
56 | - Automatically create [clientLibrary resources](https://experienceleague.adobe.com/docs/experience-manager-65/developing/introduction/clientlibs.html?lang=en) based on the source files.
57 | - Include all generated CSS and JS files in the css.txt and js.txt files.
58 |
59 | ## Guides
60 |
61 | + [Configuration](./docs/configuration.md)
62 | + [Recommended NPM Tasks](./docs/tasks.md)
63 | + [Quick Start](./docs/quick_start.md)
64 | + [Migrating to v4.0.0](./docs/migration.md)
65 | + [Contributing](./docs/CONTRIBUTING.md)
66 |
67 |
--------------------------------------------------------------------------------
/utils/renderPostcss.test.js:
--------------------------------------------------------------------------------
1 |
2 | const fs = require('fs');
3 | const path = require('path');
4 | const defaults = require('../config');
5 | const extendConfig = require('../utils/extendConfig');
6 | const renderPostcss = require('./renderPostcss');
7 | // prevent logs from extend config
8 | console.log = jest.fn();
9 | const config = extendConfig('./test/.febuild', defaults);
10 | const outFile = path.resolve(`./test/dist/postcss/component.dist.css`);
11 | const inputContent = {css: `::placeholder {
12 | color: gray;
13 | }
14 |
15 | .image {
16 | background-image: url(image@1x.png);
17 | }
18 |
19 | @media (min-resolution: 2dppx) {
20 | .image {
21 | background-image: url(image@2x.png);
22 | }
23 | }`};
24 |
25 | const outputContent = '::-moz-placeholder {\n' +
26 | ' color: gray;\n' +
27 | '}\n' +
28 | '\n' +
29 | '::placeholder {\n' +
30 | ' color: gray;\n' +
31 | '}\n' +
32 | '\n' +
33 | '.image {\n' +
34 | ' background-image: url(image@1x.png);\n' +
35 | '}\n' +
36 | '\n' +
37 | '@media (min-resolution: 2dppx) {\n' +
38 | ' .image {\n' +
39 | ' background-image: url(image@2x.png);\n' +
40 | ' }\n' +
41 | '}';
42 |
43 | sourceMapOutput = `
44 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvZGlzdC9wb3N0Y3NzL2NvbXBvbmVudC5kaXN0LmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtJQUNJLFdBQVc7QUFDZjs7QUFGQTtJQUNJLFdBQVc7QUFDZjs7QUFFQTtJQUNJLG1DQUFtQztBQUN2Qzs7QUFFQTtJQUNJO1FBQ0ksbUNBQW1DO0lBQ3ZDO0FBQ0oiLCJmaWxlIjoidGVzdC9kaXN0L3Bvc3Rjc3MvY29tcG9uZW50LmRpc3QuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiOjpwbGFjZWhvbGRlciB7XG4gICAgY29sb3I6IGdyYXk7XG59XG5cbi5pbWFnZSB7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltYWdlQDF4LnBuZyk7XG59XG5cbkBtZWRpYSAobWluLXJlc29sdXRpb246IDJkcHB4KSB7XG4gICAgLmltYWdlIHtcbiAgICAgICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltYWdlQDJ4LnBuZyk7XG4gICAgfVxufSJdfQ== */`;
45 |
46 | describe('Test utils/renderPostcss.js', () => {
47 | it(`Postcss render autoprefix plugin`, async () => {
48 | config.postcss.failOnError = false;
49 | console.log = jest.fn();
50 | await renderPostcss(inputContent, outFile, config, (r) => {
51 | expect(r.css).toBe(outputContent);
52 | });
53 | });
54 | it(`Postcss render autoprefix plugin with source maps`, async () => {
55 | config.postcss.failOnError = false;
56 | config.general.isProduction = false;
57 | console.log = jest.fn();
58 | await renderPostcss({...inputContent, map:''}, outFile, config, (r) => {
59 | expect(r.css).toBe(outputContent + sourceMapOutput);
60 | });
61 | // return config
62 | config.general.isProduction = true;
63 |
64 | });
65 |
66 | it(`Postcss should return errors by loggin when failOnError is false`, async () => {
67 | config.postcss.failOnError = false;
68 | console.error = jest.fn();
69 | console.log = jest.fn();
70 | await renderPostcss('s', 's', config, (r) =>r);
71 | expect((console.error.mock.calls[0] || console.log.mock.calls[0]).length).toBeGreaterThan(0);
72 | });
73 |
74 | it(`Should Should exit 1 with erros failOnError`, async () => {
75 | const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
76 | config.postcss.failOnError = true;
77 | await renderPostcss(false, false, config);
78 | expect(mockExit).toHaveBeenCalledWith(1);
79 | });
80 | })
81 |
--------------------------------------------------------------------------------
/config/general.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const getArgumentValue = require('../utils/getArgumentValue');
3 | /*
4 |
5 | paths configurations
6 |
7 | */
8 | // Project key for prefix and folders
9 | const projectKey = 'myproj';
10 |
11 | // root path of the package
12 | const rootPath = path.resolve('.');
13 |
14 | // source files path
15 | const sourcesPath = path.join(rootPath, 'src');
16 |
17 | // common folder for alias like import 'commons/utils/myFn';
18 | const common = path.join(sourcesPath, 'common');
19 |
20 | // paths that should be excluded from any search (defaults ones)
21 | const ignore = ['!(**/target/**)', '!(**/jcr_root/**)', '!(**/common/**)'];
22 |
23 | // where files should be compiled at
24 | const destinationPath = path.join(rootPath, 'dist');
25 |
26 | // Node modules for alias lower case for
27 | const nodeModules = path.join(rootPath, 'node_modules');
28 |
29 | /*
30 |
31 | files entries configurations
32 |
33 | */
34 | // what is the source files key suffix to compile
35 | const sourceKey = 'source';
36 |
37 | // what is the compiled bundle key
38 | const bundleKey = 'bundle';
39 |
40 | // source file types ['js', 'scss']
41 | const sourceTypes = ['js', 'scss'];
42 |
43 | // project local configurations for sub folders so it compile as a bundle
44 | const extendConfigurations = '.febuild';
45 |
46 | // default tasks to run
47 | // optional clientLibs
48 | const defaultTasks = ['styles', 'webpack', 'clientlibs'];
49 |
50 | /*
51 |
52 | command line arguments configurations
53 |
54 | */
55 | // By default its production, then use the flag or node env to change it
56 | const isProduction = !(process.env.NODE_ENV === 'development')
57 | && !getArgumentValue('--development');
58 |
59 | // Mode is string format
60 | const mode = isProduction ? 'production' : 'development';
61 |
62 | // check watchers flag
63 | const watch = getArgumentValue('--watch');
64 |
65 | // bundle analyze flag
66 | const analyze = getArgumentValue('--analyze');
67 |
68 | // task to execute via command line
69 | const task = getArgumentValue('--task=');
70 |
71 | // select config path params command line
72 | const configFile = getArgumentValue('--config-file=');
73 |
74 | // quiet
75 | const quiet = getArgumentValue('--quiet');
76 |
77 | // analyzerPort
78 | const analyzerPort = getArgumentValue('--port=') || 8888;
79 |
80 | // disable Stylelint
81 | const disableStyleLint = getArgumentValue('--disable-styelint');
82 |
83 | // general webpack
84 | const devtool = isProduction ? false : 'inline-source-map';
85 |
86 | // general optimization
87 | const excludedFromVendors = ['babel', 'core-js'];
88 |
89 | // modules to run on webpack rules (each config module.config.js)
90 | const modules = ['babel'];
91 |
92 | // split all entries
93 | const multiple = true;
94 |
95 | // export as a hole object
96 | module.exports = {
97 | projectKey,
98 | sourceKey,
99 | bundleKey,
100 | sourceTypes,
101 | rootPath,
102 | sourcesPath,
103 | common,
104 | ignore,
105 | destinationPath,
106 | isProduction,
107 | mode,
108 | watch,
109 | analyze,
110 | analyzerPort,
111 | disableStyleLint,
112 | task,
113 | quiet,
114 | configFile,
115 | extendConfigurations,
116 | modules,
117 | multiple,
118 | nodeModules,
119 | defaultTasks,
120 | devtool,
121 | excludedFromVendors
122 | };
123 |
--------------------------------------------------------------------------------
/docs/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Cognizant Netcentric Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at Grp-opensourceoffice@adobe.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: https://contributor-covenant.org
74 | [version]: https://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/docs/migration.md:
--------------------------------------------------------------------------------
1 |
2 | # Migrating to v4.0.0
3 |
4 | This release contains breaking changes. We know these can be disruptive, but they were needed to keep the dependencies updated.
5 |
6 | ### using [Node 17+](https://nodejs.org/download/release/v18.18.2/)
7 | breaking changes may also affect you:
8 | - webpack output uses [crypto.createHash that utilize MD4 of a OpenSSL 3.0 Legacy provider]()
9 |
10 | Those You will see this error:
11 | ```bash
12 | [webpack-cli] Error: error:0308010C:digital envelope routines::unsupported
13 | at new Hash (node:internal/crypto/hash:67:19)
14 | ```
15 |
16 | #### Workaround one:
17 | ```javascript
18 |
19 | // uses a new hash function
20 | // at output.config or at .febuild output property
21 | {
22 | hashFunction: "xxhash64"
23 | }
24 | ```
25 | #### Workaround two:
26 | ```bash
27 | # At your terminal, profile or before your build function run / use
28 | export NODE_OPTIONS=--openssl-legacy-provider;
29 | ```
30 |
31 |
32 | ### [ESlint ^8](https://eslint.org/docs/latest/use/migrate-to-8.0.0)
33 | Build now uses [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) intead of deprecated [eslint-loader](https://www.npmjs.com/package/eslint-loader)
34 | It's setting the plugin now instead of loader then
35 | #### breaking changes
36 | - review your .febuild exported eslint property to use only the [eslint options schema](https://eslint.org/docs/latest/integrate/nodejs-api#-new-eslintoptions)
37 |
38 |
39 |
40 | ### [Stylelint ^15](https://stylelint.io/migration-guide/to-15/)
41 |
42 | We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) that includes the appropriate syntax to lint Scss.
43 |
44 | Three breaking changes may also affect you:
45 | - Upgrade [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) to "^2.0.0",
46 | - removed processors configuration property
47 | - removed support for Node.js 12
48 | - changed overrides.extends behavior
49 |
50 | ### [Webpack ^5](https://webpack.js.org/migrate/5/) and [babel v7](https://babeljs.io/docs/v7-migration)
51 |
52 | Please review your webpack config options accordenly
53 | - Review package.json dependecies on your project to match current one
54 | - review babel configs to respect [babel v7](https://babeljs.io/docs/v7-migration)
55 | - review your .febuild exported properties to use webpack latest options
56 | - [stats](https://webpack.js.org/configuration/stats/)
57 | - [cache](https://webpack.js.org/configuration/cache/)
58 | - [devServer](https://webpack.js.org/configuration/dev-server/)
59 | - [performance](https://webpack.js.org/configuration/performance/)
60 | - [resolve](https://webpack.js.org/configuration/resolve/)
61 | - [optimization](https://webpack.js.org/configuration/optimization/)
62 | - [plugins](https://webpack.js.org/configuration/plugins/)
63 |
64 |
65 |
66 | # Migrating to v2.0.0
67 |
68 | This release contains breaking changes. We know these can be disruptive, but they were needed to keep the dependencies updated.
69 |
70 | [Stylelint v14](https://stylelint.io/migration-guide/to-14/) does not longer includes the syntaxes that parse CSS-like languages like Scss. You will need to install and configure these syntaxes in your project. We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) that includes the appropriate syntax to lint Scss.
71 |
72 | First, install the shared configuration as a dependency:
73 |
74 | ```bash
75 | npm install --save-dev @netcentric/stylelint-config
76 | ```
77 |
78 | Then, update your [Stylelint configuration object](https://stylelint.io/user-guide/configure/) to use it:
79 |
80 | ```json
81 | {
82 | "extends": "@netcentric/stylelint-config",
83 | "rules": {
84 | ...
85 | }
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration file
2 |
3 | The default configuration can be extended via a `.febuild` file.
4 | This configuration file is loaded and executed as a JavaScript module.
5 | It is used for all files located in the same directory as the `.febuild` file and in the subdirectory tree.
6 |
7 | You can add multiple `.febuild` whenever you need to run a separate build with other options.
8 |
9 | These are the configurations that can be extended:
10 |
11 | - general
12 | - output
13 | - [optimization](https://webpack.js.org/configuration/optimization/)
14 | - [plugins](https://webpack.js.org/configuration/plugins/)
15 | - eslint
16 | - babel
17 | - sass
18 | - clientlibs
19 | - stylelint
20 | - [resolve](https://webpack.js.org/configuration/resolve/)
21 | - postcss
22 | - templates
23 |
24 | Other webpack options
25 |
26 | - [stats](https://webpack.js.org/configuration/stats/)
27 | - [cache](https://webpack.js.org/configuration/cache/)
28 | - [devServer](https://webpack.js.org/configuration/dev-server/)
29 | - [performance](https://webpack.js.org/configuration/performance/)
30 |
31 | ## Overriding a Default Configuration
32 |
33 | To override a configuration, add a new entry in the object exported in your `.febuild` with the name of the configuration you want to override, which value is an object whith entries that matches the existing options you want to override.
34 |
35 | E.g., to override the default Babel configuration with new `exclude` paths and plugins, you can do the following in your `.febuild` file:
36 |
37 | ```javascript
38 | module.exports = {
39 | babel: {
40 | exclude: /node_modules\/(?!swiper|dom7)/,
41 | use: {
42 | options: {
43 | plugins: ['@babel/plugin-proposal-optional-chaining', '@babel/plugin-transform-runtime', '@babel/plugin-proposal-object-rest-spread']
44 | }
45 | }
46 | }
47 | };
48 | ```
49 |
50 | Default configuration can be extended by using a function instead of an object, which accepts an argument that is the default configuration.
51 |
52 | ```javascript
53 | module.exports = (defaultConfig) => ({
54 | babel: {
55 | use: {
56 | options: {
57 | plugins: ['@babel/plugin-proposal-optional-chaining', ...defaultConfig.babel.use.options]
58 | }
59 | }
60 | }
61 | });
62 | ```
63 |
64 | ## Configurations
65 |
66 | ### General
67 |
68 | This configuration part is used for basic project setup. You will find an explanation of each key as a comment next to it.
69 | Defaults:
70 |
71 | ```javascript
72 | module.exports = {
73 | general: {
74 | // Your project name with which ClientLibs category are prefixed
75 | projectKey: "myproj",
76 | // Only the source files with this suffix will be compiled
77 | sourceKey: "source",
78 | // The compiled bundle filename suffix
79 | bundleKey: "bundle",
80 | // The path to the directory with your source files
81 | sourcesPath: "src",
82 | // Path to the dir with the code shared among Scss and JS files
83 | common: "common",
84 | // Paths to ignore when the build looks for files to compile
85 | ignore: ["!(**/target/**)", "!(**/jcr_root/**)", "!(**/common/**)"],
86 | // Destination of the compiled files
87 | destinationPath: "dist",
88 | // Name of the configuration file
89 | extendConfigurations: ".febuild",
90 | // Modules to run with webpack, each being a config file (e.g. eslint.config.js)
91 | modules: [ "eslint", "babel" ],
92 | // The tasks to run when executing `npm run build`
93 | defaultTasks: [ "styles", "webpack", "clientlibs" ],
94 | }
95 | }
96 | ```
97 |
98 | Example of overriding the `defaultTasks` to exclude the 'clientlibs' task:
99 |
100 | ```javascript
101 | module.exports = {
102 | general: {
103 | defaultTasks: ['styles', 'webpack']
104 | }
105 | }
106 | ```
107 |
108 | ### Babel
109 |
110 | Babel webpack plugin, enabled by default in the option `general.modules`.
111 | For more information about the configuration options check [babel-loader](https://github.com/babel/babel-loader).
112 |
113 | ```javascript
114 | {
115 | babel: {
116 | enforce: 'post',
117 | test: /\.js$/,
118 | exclude: /node_modules\/(?!@netcentric)/,
119 | use: {
120 | loader: 'babel-loader',
121 | options: {
122 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]],
123 | plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-object-rest-spread']
124 | }
125 | }
126 | }
127 | }
128 | ```
129 |
130 | #### @babel/preset-env
131 |
132 | By default `@babel/preset-env` will use [`browserslist` config sources](https://github.com/browserslist/browserslist) _unless_ either the `targets` or `ignoreBrowserslistConfig` options are set.
133 |
134 | > When no browser targets are specified, Babel will assume the oldest browsers possible, which will increase the output code size.
135 |
136 | We recommend using a [.browserslistrc](https://github.com/browserslist/browserslist#browserslistrc) file to specify the targets.
137 |
138 | **`useBuiltIns: usage`**
139 |
140 | This option configures how Babel [handles the polyfills](https://babeljs.io/docs/en/babel-preset-env#usebuiltins), by adding the specific imports only when the polyfill is used in each file.
141 |
142 | What is the advantages over "entry"?
143 | - It allows proper tree-shaking the polyfills
144 | - Reduce the size of the JavaScript file entrypoint
145 |
146 | #### Core JS 3
147 |
148 | [core-js 3](https://github.com/zloirock/core-js) is included by default, and set in the options of `@babel/preset-env` to import only the polyfills used in your code.
149 |
150 | Hence **you don't need to import core-js in your project**, or code duplication will happen increasing the size of the output code.
151 |
152 | ### Stylelint
153 |
154 | Stylelint is a CSS linter which can also lint Scss files.
155 |
156 | Default configuration:
157 |
158 | ```javascript
159 | module.exports = {
160 | {
161 | stylelint: {
162 | // Stops the build if a linter error is found
163 | failOnError: true
164 | }
165 | }
166 | }
167 | ```
168 |
169 | Please note that you need a [Stylelint configuration object]((https://stylelint.io/user-guide/configure/)) to parse CSS-like languages like Scss. We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config).
170 |
171 | You can add your own linter rules in the [Stylelint configuration object](https://stylelint.io/user-guide/configure/#rules).
172 |
173 | ### ESlint
174 |
175 | ESLint statically analyzes your code to quickly find problems.
176 |
177 | For more information about the configuration options check [eslint-loader](https://github.com/webpack-contrib/eslint-loader).
178 |
179 | ```javascript
180 | module.exports = {
181 | failOnError: true,
182 | fix: true // deprecated with scss only for css
183 | }
184 | ```
185 |
186 | ### Sass
187 |
188 | [Sass](https://sass-lang.com/) is the default CSS preprocessor supported by the fe-build.
189 |
190 | ```javascript
191 | {
192 | sass: {
193 | // Paths where Sass will look for stylesheets (when using @import and @use)
194 | includePaths: [path.join(common, 'styles'), nodeModules],
195 | // The output style of the compiled CSS: "expanded, compressed, nested or compact
196 | outputStyle: isProduction ? 'compressed' : 'expanded'
197 | }
198 | }
199 | ```
200 |
201 | ### PostCSS
202 |
203 | [PostCSS](https://postcss.org/) is used to transform the CSS code generated after the Sass compilation.
204 |
205 | Default configuration:
206 |
207 | ```javascript
208 | {
209 | postcss: {
210 | // Default plugins
211 | plugins: ['autoprefixer', ['another-postcss-plugin',{ foo: 'bar'}]],
212 | // Stops the build if an error is found
213 | failOnError: true
214 | }
215 | }
216 | ```
217 |
218 | You can add new PostCSS plugins by overriding the option `plugins` in your `.febuild` file. Place them _before_ the autoprefixer plugin.
219 |
220 | ```javascript
221 | postcss: {
222 | plugins: ['mypostcssplugin', 'autoprefixer']
223 | }
224 | ```
225 |
226 | ### Plugins
227 |
228 | This configuration part refers to [Webpack plugins](https://webpack.js.org/plugins/define-plugin/).
229 |
230 | Default configuration:
231 |
232 | ```javascript
233 | {
234 | plugins: [
235 | new webpack.DefinePlugin({
236 | 'process.env.NODE_ENV': JSON.stringify(mode)
237 | }
238 | ]
239 | }
240 | ```
241 |
242 | You can add new plugins by overriding the option `plugins` or pushing them into the default configuration.
243 |
244 | ```javascript
245 | module.exports = (defaultConfig) => ({
246 | plugins: [...defaultConfig.plugins, myPlugin]
247 | });
248 | ```
249 |
250 | ### Treeshaking, Commons and Vendors
251 |
252 | Webpack optimizations for your JavaScript code: minimization, code splitting and tree shaking.
253 | Default configuration:
254 |
255 | ```javascript
256 | {
257 | optimization: {
258 | minimize: isProduction,
259 | usedExports: isProduction,
260 | runtimeChunk: {
261 | name: 'commons/treeshaking.bundle.js'
262 | },
263 | splitChunks: {
264 | cacheGroups: {
265 | // this treeshake vendors (but keep unique vendors at the clientlibs it belongs )
266 | vendors: {
267 | test: mod => moduleIsVendor(mod.context, excludedFromVendors),
268 | name: 'commons/vendors.bundle.js',
269 | chunks: 'all',
270 | minChunks: 2
271 | },
272 | // this treeshakes common imports, if are and more than 2 clientlibs
273 | treeshaking: {
274 | test: mod => !moduleIsVendor(mod.context, excludedFromVendors),
275 | name: 'commons/treeshaking.bundle.js',
276 | chunks: 'all',
277 | minChunks: 2
278 | }
279 | }
280 | }
281 | }
282 | }
283 | ```
284 |
285 | If a module is imported into more than 2 files, it's extracted to a common file.
286 | There are 2 main common files:
287 |
288 | - `treeshaking.bundle.js`: common code that reside in the project.
289 | - `vendors.bundle.js`: common code that is outside of the scope your project.
290 |
291 | **vendors.bundle.js**
292 |
293 | This is intended to extract reused third-party scripts that are imported in two or more modules to a different file, so it avoids duplication and this file can be loaded separately.
294 | Also it is good to separate those third-party from the regular tree shaking since this vendors sometimes might be best suited as an external option.
295 |
296 | Advantages of having a separated vendor:
297 | - Clear view of the impact of third-party on your code base.
298 | - You can identify possible additions to externals (removing it completely from your code).
299 |
300 | **treeshaking.bundle.js**
301 |
302 | This is intended to optimize the codebase of the project, by code splitting the modules that are the building blocks of it, like core-js, babel and @your modules.
303 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2022 Cognizant Netcentric
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [5.3.1](https://github.com/Netcentric/fe-build/compare/v5.3.0...v5.3.1) (2025-05-15)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * **utils:** Fix incorrect metadata content for postcss extraEntries ([d524dce](https://github.com/Netcentric/fe-build/commit/d524dceb44799a3f92af75d06b2b453019c82993))
7 |
8 | # [5.3.0](https://github.com/Netcentric/fe-build/compare/v5.2.0...v5.3.0) (2025-05-09)
9 |
10 |
11 | ### Features
12 |
13 | * **config:** Support clientlib metadata generation for dynamically created CSS files ([81a7229](https://github.com/Netcentric/fe-build/commit/81a7229757568abea848f9aaddb8eb055445eaf2))
14 |
15 | # [5.2.0](https://github.com/Netcentric/fe-build/compare/v5.1.3...v5.2.0) (2025-05-05)
16 |
17 |
18 | ### Features
19 |
20 | * **utils:** support plugins with configuration objects ([3eeeec8](https://github.com/Netcentric/fe-build/commit/3eeeec818fb8cf49d228259b8e5eda1aec11a1be))
21 |
22 | ## [5.1.3](https://github.com/Netcentric/fe-build/compare/v5.1.2...v5.1.3) (2025-04-16)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * [#112](https://github.com/Netcentric/fe-build/issues/112) - update jest timeout for webpack ([a34e790](https://github.com/Netcentric/fe-build/commit/a34e7903115958c224366ee48a876c92028c2d77))
28 | * [#112](https://github.com/Netcentric/fe-build/issues/112) - update source maps for new sass version ([2ba91fd](https://github.com/Netcentric/fe-build/commit/2ba91fd88c44a5b78bcff1a71d6fce5f59377084))
29 |
30 | ## [5.1.2](https://github.com/Netcentric/fe-build/compare/v5.1.1...v5.1.2) (2025-04-10)
31 |
32 |
33 | ### Bug Fixes
34 |
35 | * **utils:** Fix category name generation to prevent consecutive dots and leading dots ([8b85be7](https://github.com/Netcentric/fe-build/commit/8b85be7dee5f1af519d6d2962da25baae4d270a4))
36 |
37 | ## [5.1.1](https://github.com/Netcentric/fe-build/compare/v5.1.0...v5.1.1) (2025-03-13)
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * **config:** change package namespace in babel.config exclude property ([f229c9e](https://github.com/Netcentric/fe-build/commit/f229c9e32bbf09d1492dd55a9e7fa2a857eef4c0))
43 |
44 | # [5.1.0](https://github.com/Netcentric/fe-build/compare/v5.0.0...v5.1.0) (2025-03-13)
45 |
46 |
47 | ### Features
48 |
49 | * ([#112](https://github.com/Netcentric/fe-build/issues/112)) update legacy-js-api, ([#69](https://github.com/Netcentric/fe-build/issues/69)) add option of .febuild.js, fix [#19](https://github.com/Netcentric/fe-build/issues/19) ([4624297](https://github.com/Netcentric/fe-build/commit/4624297b5d8df680076cceaf68e03a6175a39f05))
50 |
51 | # [5.0.0](https://github.com/Netcentric/fe-build/compare/v4.0.2...v5.0.0) (2025-01-29)
52 |
53 |
54 | ### Bug Fixes
55 |
56 | * fix tests ([3f74d3e](https://github.com/Netcentric/fe-build/commit/3f74d3eea4ddab51df1bc9a4ca098a64d0896b23))
57 | * update dependencies ([2108e53](https://github.com/Netcentric/fe-build/commit/2108e532eb6aaec807b4180ffa1c1da712ee0b02))
58 |
59 |
60 | * Merge pull request #114 from Netcentric/feature/update-stylelint ([179ca94](https://github.com/Netcentric/fe-build/commit/179ca9492e70cefba622e5cd8a03cb1ee0d35971)), closes [#114](https://github.com/Netcentric/fe-build/issues/114)
61 |
62 |
63 | ### BREAKING CHANGES
64 |
65 | * Update stylelint to v16
66 | * Update stylelint to v16
67 |
68 | ## [4.0.2](https://github.com/Netcentric/fe-build/compare/v4.0.1...v4.0.2) (2024-12-10)
69 |
70 |
71 | ### Bug Fixes
72 |
73 | * override semver ([2358226](https://github.com/Netcentric/fe-build/commit/2358226aa1f3f652fdd011b081d0aa3fba8de187))
74 | * update dependencies ([93520ff](https://github.com/Netcentric/fe-build/commit/93520ff8ef5538a6927650806323ee7488a5745e))
75 |
76 | ## [4.0.1](https://github.com/Netcentric/fe-build/compare/v4.0.0...v4.0.1) (2024-01-31)
77 |
78 |
79 | ### Bug Fixes
80 |
81 | * Add webpack tests ([aa972bc](https://github.com/Netcentric/fe-build/commit/aa972bca064553cf4bda978b143d2479a6e03241))
82 | * clone arrays in merge utils ([5c432f8](https://github.com/Netcentric/fe-build/commit/5c432f823455403872b1327ae50210741f53d259))
83 |
84 | # [4.0.0](https://github.com/Netcentric/fe-build/compare/v3.1.0...v4.0.0) (2023-10-19)
85 |
86 |
87 | * BREAKING CHANGE: Trigger Manual Release ([4873a27](https://github.com/Netcentric/fe-build/commit/4873a279c488fb0a7689e6676a9d494c7dc022d3))
88 |
89 |
90 | ### BREAKING CHANGES
91 |
92 | * Forced Manual Release without code changes
93 |
94 | # [3.1.0](https://github.com/Netcentric/fe-build/compare/v3.0.4...v3.1.0) (2023-10-19)
95 |
96 |
97 | ### Bug Fixes
98 |
99 | * update stylelint ([ddec6b5](https://github.com/Netcentric/fe-build/commit/ddec6b5464c9cda2e5e5460d286bc069516fcb40))
100 |
101 |
102 | ### Features
103 |
104 | * **utils:** upgrade to webpack 5 ([81d28ca](https://github.com/Netcentric/fe-build/commit/81d28ca56595074e8f15e3eb2046c3e27ccae877))
105 |
106 | ## [3.0.4](https://github.com/Netcentric/fe-build/compare/v3.0.3...v3.0.4) (2023-10-09)
107 |
108 |
109 | ### Bug Fixes
110 |
111 | * **contribution:** fix preinstall script that was only required to contribution and break npm i windows install ([5ca1c2a](https://github.com/Netcentric/fe-build/commit/5ca1c2a20992c651146379f967c736ac60848989))
112 |
113 | ## [3.0.2](https://github.com/Netcentric/fe-build/compare/v3.0.1...v3.0.2) (2023-01-25)
114 |
115 |
116 | ### Bug Fixes
117 |
118 | * change preinstall script to account for missing .git/hooks folders ([07e8362](https://github.com/Netcentric/fe-build/commit/07e83627a1bc1026261315360da9c9e4a2e851a4))
119 |
120 | ## [3.0.1](https://github.com/Netcentric/fe-build/compare/v3.0.0...v3.0.1) (2022-10-21)
121 |
122 |
123 | ### Bug Fixes
124 |
125 | * **hooks:** [#82](https://github.com/Netcentric/fe-build/issues/82) add hooks to package files ([0ef08ac](https://github.com/Netcentric/fe-build/commit/0ef08aca3ce45616160e56a27ffdcd598d94ff79))
126 |
127 | # [3.0.0](https://github.com/Netcentric/fe-build/compare/v2.1.0...v3.0.0) (2022-08-25)
128 |
129 |
130 | ### Bug Fixes
131 |
132 | * Changes values defined in `SourceMapOptions` to match PostCSS v8 API ([bf0344d](https://github.com/Netcentric/fe-build/commit/bf0344dd4f1a513e7327ccf11c147da7d9216703))
133 | * Move gaze to regular depedencies ([d2e71af](https://github.com/Netcentric/fe-build/commit/d2e71af7c0b35d4d1d6fd6bd7ec11eb8bece8896)), closes [#65](https://github.com/Netcentric/fe-build/issues/65)
134 | * Removes IE Mobile from browserlist ([389bbd4](https://github.com/Netcentric/fe-build/commit/389bbd4f625ba40e08f39853831f97cc03180b7c))
135 | * update missing changelog plugin version ([a58f949](https://github.com/Netcentric/fe-build/commit/a58f94932b233bad8d5f26b18a844fc7c90f490b))
136 | * Updates eslint-plugin-import because current version is not supported by eslint v7 ([34c4d37](https://github.com/Netcentric/fe-build/commit/34c4d373206a7650ae61a367fbc57ee79147a192))
137 | * **utils:** adjust custom missing destination basePath when custom sourcesPath defined ([9cd6d0f](https://github.com/Netcentric/fe-build/commit/9cd6d0fbbe3e9c28ea2b3a81ccb303f08d6c316c))
138 |
139 |
140 | ### Feat
141 |
142 | * Bump major version number ([d06c2d4](https://github.com/Netcentric/fe-build/commit/d06c2d44da0018c76ab16891237eb2f870a5ce94))
143 |
144 |
145 | ### Features
146 |
147 | * Adds migration guide ([59e987d](https://github.com/Netcentric/fe-build/commit/59e987d93291e0e5add95227aee829a5b58df954))
148 | * Adds supported node.js versions ([30ee448](https://github.com/Netcentric/fe-build/commit/30ee448973467e779bb2c6fde7019e71270fd71e))
149 | * Updates stylelint to v14.6.1 and removes obsolete `syntax` option ([930dc8e](https://github.com/Netcentric/fe-build/commit/930dc8e319dd3ebc7b2cf970f1c37190fcbcaf0d))
150 |
151 |
152 | ### BREAKING CHANGES
153 |
154 | * Stylelint v14 does not include syntaxes by default
155 |
156 | Stylelint no longer includes the syntaxes to parse CSS-like languages like SCSS.
157 | Migration guide:
158 | - In your Stylelint configuration object extend a shared config like @netcentric/stylelint-config
159 |
160 | # [2.1.0](https://github.com/Netcentric/fe-build/compare/v2.0.2...v2.1.0) (2022-08-18)
161 |
162 |
163 | ### Features
164 |
165 | * make Stylelint optional ([d344705](https://github.com/Netcentric/fe-build/commit/d34470560114b034f919c29ffb258710810d02a6))
166 |
167 | ## [2.0.2](https://github.com/netcentric/fe-build/compare/v2.0.1...v2.0.2) (2022-07-18)
168 |
169 |
170 | ### Bug Fixes
171 |
172 | * **utils:** adjust custom missing destination basePath when custom sourcesPath defined ([97ed7d4](https://github.com/netcentric/fe-build/commit/97ed7d4de64da146866c625a1d9cabad26099129))
173 |
174 | ## [2.0.1](https://github.com/netcentric/fe-build/compare/v2.0.0...v2.0.1) (2022-07-12)
175 |
176 |
177 | ### Bug Fixes
178 |
179 | * Move gaze to regular depedencies ([165c90b](https://github.com/netcentric/fe-build/commit/165c90b4665c941b3a223d7ec2b75bb380147910)), closes [#65](https://github.com/netcentric/fe-build/issues/65)
180 |
181 | # [2.0.0](https://github.com/netcentric/fe-build/compare/v1.2.0...v2.0.0) (2022-05-05)
182 |
183 |
184 | ### Bug Fixes
185 |
186 | * Removes IE Mobile from browserlist ([344d97e](https://github.com/netcentric/fe-build/commit/344d97e3e1af3839b1a2d30b742006ab998b87c3))
187 | * Updates eslint-plugin-import because current version is not supported by eslint v7 ([2b6aeff](https://github.com/netcentric/fe-build/commit/2b6aeff713840c97a3fdb1c2f67b0911ab01575e))
188 |
189 |
190 | ### Feat
191 |
192 | * Bump major version number ([c8ca422](https://github.com/netcentric/fe-build/commit/c8ca42259f659626a65ca55568262563dfb7f968))
193 |
194 |
195 | ### Features
196 |
197 | * Adds migration guide ([e537ab1](https://github.com/netcentric/fe-build/commit/e537ab1f2ac12ecfb370459af66f98d9b4a38576))
198 | * Adds supported node.js versions ([31e9371](https://github.com/netcentric/fe-build/commit/31e93719b3c6263b42bed86545e8a70782fb77f0))
199 | * Updates stylelint to v14.6.1 and removes obsolete `syntax` option ([18742aa](https://github.com/netcentric/fe-build/commit/18742aadba82e5b83c7995e6a5c6b145101bf490))
200 |
201 |
202 | ### BREAKING CHANGES
203 |
204 | * Stylelint v14 does not include syntaxes by default
205 |
206 | Stylelint no longer includes the syntaxes to parse CSS-like languages like Scss.
207 | Migration guide:
208 | - In your Stylelint configuration object extend a shared config like @netcentric/stylelint-config
209 |
210 | # [1.2.0](https://github.com/netcentric/fe-build/compare/v1.1.3...v1.2.0) (2022-05-05)
211 |
212 |
213 | ### Features
214 |
215 | * replaces hardcoded path separator with `path.sep`in order to make the build work in any OS ([a4f58a8](https://github.com/netcentric/fe-build/commit/a4f58a84f05abad4e6692a1167959b7cedaf16e4))
216 |
217 | ## [1.1.3](https://github.com/netcentric/fe-build/compare/v1.1.2...v1.1.3) (2022-04-24)
218 |
219 |
220 | ### Bug Fixes
221 |
222 | * Changes values defined in `SourceMapOptions` to match PostCSS v8 API ([d65132f](https://github.com/netcentric/fe-build/commit/d65132fb69b781c643ae5761f9ae5cc415025b9c))
223 |
224 | ## [1.1.2](https://github.com/netcentric/fe-build/compare/v1.1.1...v1.1.2) (2022-02-14)
225 |
226 |
227 | ### Bug Fixes
228 |
229 | * change cli path ([ab31b00](https://github.com/netcentric/fe-build/commit/ab31b00266cd71c7b2f5a38778767a083aea9ad7))
230 | * update missing changelog plugin version ([151c1ea](https://github.com/netcentric/fe-build/commit/151c1ea2c90308ffdb0cabeb35e8d15a568932af))
231 |
232 | ## [1.1.1](https://github.com/netcentric/fe-build/compare/v1.1.0...v1.1.1) (2022-02-14)
233 |
234 |
235 | ### Bug Fixes
236 |
237 | * include index.js ([81cef07](https://github.com/netcentric/fe-build/commit/81cef07e3e38aa1a20dab0da51e651c1e0b85c24))
238 |
239 | # [1.1.0](https://github.com/netcentric/fe-build/compare/v1.0.2...v1.1.0) (2022-02-13)
240 |
241 |
242 | ### Bug Fixes
243 |
244 | * move to node v16, update package-lock with v2 ([fd1fafc](https://github.com/netcentric/fe-build/commit/fd1fafc37c6e8372002314a74ccc47d3eae846e5))
245 | * update actions for node v16 ([7c21f6c](https://github.com/netcentric/fe-build/commit/7c21f6c7d8cd7bc1c71da3f42ade5bd3053a8f59))
246 | * update eslint ([14698c0](https://github.com/netcentric/fe-build/commit/14698c019455d926ff249e546051d220a4f14acc))
247 |
248 |
249 | ### Features
250 |
251 | * fix dependencies versions ([18a5c9d](https://github.com/netcentric/fe-build/commit/18a5c9d948e3f9f1b20b9b77c815b81f72f43c84))
252 |
253 | ## [1.0.2](https://github.com/netcentric/fe-build/compare/v1.0.1...v1.0.2) (2021-07-09)
254 |
255 |
256 | ### Bug Fixes
257 |
258 | * add package.json to automated release updates ([7c1515e](https://github.com/netcentric/fe-build/commit/7c1515e9a2dce6be182aca0333335e64350ac859))
259 |
260 | ## [1.0.1](https://github.com/netcentric/fe-build/compare/v1.0.0...v1.0.1) (2021-05-26)
261 |
262 |
263 | ### Bug Fixes
264 |
265 | * dist path replace ([51d720c](https://github.com/netcentric/fe-build/commit/51d720ce0b80c6a8d77b64b248a47e2367e8d4d5))
266 |
267 | # 1.0.0 (2021-05-21)
268 |
269 |
270 | ### Bug Fixes
271 |
272 | * remove broken image ([db009d1](https://github.com/netcentric/fe-build/commit/db009d1582d952f848fc79bec5df2072a78b2b73))
273 | * **init:** initial commit ([42cf70a](https://github.com/netcentric/fe-build/commit/42cf70af16415202ad6af297ee456d560d2b214a))
274 | * **init:** NPM_TOKEN secret info added ([5616040](https://github.com/netcentric/fe-build/commit/56160408295b486eabd14dcb81461f98231d492c))
275 |
--------------------------------------------------------------------------------