lincd-cli
Version:
Command line tools for the lincd.js library
460 lines • 20.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import path from 'path';
import chalk from 'chalk';
import ReactRefreshTypeScript from 'react-refresh-typescript';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import webpack from 'webpack';
import fs from 'fs';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import TerserPlugin from 'terser-webpack-plugin';
import { generateScopedName, generateScopedNameProduction, getLINCDDependencies, } from './utils.js';
import { LinkedFileStorage } from 'lincd/utils/LinkedFileStorage';
import postcssUrl from 'postcss-url';
// import { addLincdSourcesPlugin } from './plugins/lincd-tailwind-sources';
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';
const packageJson = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'));
const cssModes = ['scss-modules', 'tailwind', 'scss', 'mixed'];
class WatchRunPlugin {
apply(compiler) {
compiler.hooks.watchRun.tap('watchRun', (comp) => {
if (comp.modifiedFiles) {
const changedFiles = Array.from(comp.modifiedFiles);
console.log(chalk.magenta('Changed files sorted by \'modified time\' stamps:'));
const entriesToCheck = [];
changedFiles.forEach((file) => {
try {
const stat = fs.statSync(file.toString());
console.log(` ${chalk.magenta(file)}`);
entriesToCheck.push({ path: file, mtime: stat.mtime });
if (stat.isDirectory()) {
const contents = fs.readdirSync(file.toString());
contents.forEach((name) => {
const fullPath = path.join(file.toString(), name);
try {
const innerStat = fs.statSync(fullPath);
//if less than 2 minutes ago...
if (innerStat.mtime > new Date(Date.now() - 2 * 60 * 1000)) {
entriesToCheck.push({ path: fullPath, mtime: innerStat.mtime });
}
}
catch (e) {
entriesToCheck.push({ path: fullPath, mtime: new Date(0), error: e.message });
}
});
}
}
catch (err) {
entriesToCheck.push({ path: file, mtime: new Date(0), error: err.message });
}
});
entriesToCheck
.sort((a, b) => b.mtime - a.mtime)
.splice(0, 3)
.forEach((entry) => {
const label = entry.error
? `[error: ${entry.error}]`
: `[${entry.mtime.toISOString()}]`;
console.log(` ${entry.path} ${label}`);
});
}
});
}
}
/**
* Converts a css class name to a unique scoped name (for CSS Modules)
* @param context
* @param currentFormat
* @param name
*/
function getLocalIdent(context, currentFormat, name) {
return isProduction ? generateScopedNameProduction(name, context.resourcePath) : generateScopedName(name, context.resourcePath);
}
export const getLincdConfig = () => __awaiter(void 0, void 0, void 0, function* () {
const lincdConfigPathJs = path.resolve(process.cwd(), 'lincd.config.js');
const lincdConfigPathJson = path.resolve(process.cwd(), 'lincd.config.json');
//default config
let config = {
//scss-modules is default
cssMode: cssModes[0],
analyse: false,
};
//overwriting config from package.json.lincdApp or lincd.config.js(on) file
if (typeof packageJson.lincdApp === 'object') {
//overwrite default with anything that's defined in lincdApp in package.json
config = Object.assign(Object.assign({}, config), packageJson.lincdApp);
}
else if (fs.existsSync(lincdConfigPathJs)) {
let lincdConfig = yield import(lincdConfigPathJs);
config = Object.assign(Object.assign({}, config), lincdConfig.default);
}
else if (fs.existsSync(lincdConfigPathJson)) {
let lincdConfig = JSON.parse(fs.readFileSync(lincdConfigPathJson, 'utf-8'));
config = Object.assign(Object.assign({}, config), lincdConfig);
}
if (!cssModes.includes(config.cssMode)) {
console.warn('Invalid value for property cssMode. Should be one of: ' +
cssModes.join(', '));
process.exit();
}
return config;
});
export const getWebpackAppConfig = () => __awaiter(void 0, void 0, void 0, function* () {
// set up the storage config for the app
yield import(path.join(process.cwd(), 'scripts', 'storage-config.js'));
const accessURL = LinkedFileStorage.accessURL;
// set up the public path for the app
// This should match the use of express.static() in LincdServer.tsx, which makes the build files available through a URL
const publicPath = '/public';
const bundlesPath = publicPath + '/bundles/';
// ASSET_PATH is used load the assets from the correct path
const ASSET_PATH = process.env.ASSET_PATH || accessURL ? accessURL + bundlesPath : bundlesPath;
let config = yield getLincdConfig();
let postcssPlugins = [];
//tailwind first (so its processed last and doesn't overwrite custom CSS modules)
if (config.cssMode === 'tailwind' || config.cssMode === 'mixed') {
//make sure that tailwind classes from any LINCD packages that are listed in package.json:dependencies are included
let lincdPackagePaths = getLINCDDependencies(packageJson);
lincdPackagePaths = lincdPackagePaths.map(([packageName, packagePath]) => {
return packagePath + '/lib/**/*.{js,mjs}';
});
// console.log(
// chalk.blueBright('tailwind content: ') + chalk.magenta(['./frontend/src/**/*.{tsx,ts}', ...lincdPackagePaths]),
// );
postcssPlugins.push([
'@tailwindcss/postcss',
{
content: {
files: [
(process.env.SOURCE_PATH || './src/') + '**/*.{js}',
// ...lincdPackagePaths,
]
},
// config: {
// content: {
// files:[
// (process.env.SOURCE_PATH || './src/') + '**/*.{tsx,ts}',
// ...lincdPackagePaths,
// ]
// },
// // plugins:[
// // addLincdSourcesPlugin(),
// // ]
// },
// content: [
// (process.env.SOURCE_PATH || './src/') + '**/*.{tsx,ts}',
// ...lincdPackagePaths,
// ],
// safelist: isProduction
// ? {}
// : {
// //in development mode we allow all classes here, so that you can easily develop
// pattern: /./,
// variants: ['sm','md','lg','xl','2xl'],
// },
// features: {
// themeVariables: {
// generateAll: true,
// },
// },
// theme: {
// extend: {
// colors: getLinkedTailwindColors(),
// },
// },
plugins: [
// plugin(function({ addBase, theme }) {
//add styles to the base styles
//this replicates the preflight settings of tailwind v4, but without the destructive/strict #/# selectors
// addBase({
// // Reset all elements except common inline tags and semantic containers
// '*:not(code):not(pre):not(kbd):not(samp):not(mark):not(q):not(ins):not(del):not(span):not(a):not(b):not(i):not(em):not(u):not(s):not(small):not(strong):not(sub):not(sup), ::before, ::after': {
// boxSizing: 'border-box',
// margin: '0',
// padding: '0',
// borderWidth: '0',
// borderStyle: 'solid',
// borderColor: 'currentColor',
// },
// html: {
// lineHeight: '1.5',
// textSizeAdjust: '100%',
// WebkitTextSizeAdjust: '100%',
// MozTextSizeAdjust: '100%',
// fontFamily: 'system-ui, sans-serif',
// },
// body: {
// margin: '0',
// lineHeight: 'inherit',
// backgroundColor: 'white',
// },
// hr: {
// height: '0',
// color: 'inherit',
// borderTopWidth: '1px',
// },
// abbr: {
// textDecoration: 'underline dotted',
// },
// 'b, strong': {
// fontWeight: 'bolder',
// },
// 'code, kbd, samp, pre': {
// fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
// fontSize: '1em',
// },
// small: {
// fontSize: '80%',
// },
// 'sub, sup': {
// fontSize: '75%',
// lineHeight: '0',
// position: 'relative',
// verticalAlign: 'baseline',
// },
// sub: { bottom: '-0.25em' },
// sup: { top: '-0.5em' },
// table: {
// textIndent: '0',
// borderColor: 'inherit',
// borderCollapse: 'collapse',
// },
// 'button, input, optgroup, select, textarea': {
// font: 'inherit',
// color: 'inherit',
// margin: '0',
// padding: '0',
// lineHeight: 'inherit',
// backgroundColor: 'transparent',
// borderColor: 'inherit',
// },
// 'button, select': {
// textTransform: 'none',
// },
// 'button, [type="button"], [type="reset"], [type="submit"]': {
// appearance: 'button',
// WebkitAppearance: 'button',
// },
// '::-moz-focus-inner': {
// borderStyle: 'none',
// padding: '0',
// },
// ':-moz-focusring': {
// outline: 'auto',
// },
// ':-moz-ui-invalid': {
// boxShadow: 'none',
// },
// fieldset: {
// margin: '0',
// padding: '0',
// border: '0',
// },
// legend: {
// padding: '0',
// },
// });
// }),
],
// plugins: [
// tailwindPlugin(function({ addBase,config }) {
// //we can use LINCD CSS variables for default font color, size etc.
// // addBase({
// // 'h1': { fontSize: config('theme.fontSize.2xl') },
// // 'h2': { fontSize: config('theme.fontSize.xl') },
// // 'h3': { fontSize: config('theme.fontSize.lg') },
// // })
// }),
// ],
},
]);
}
else {
//if not using tailwind, then we use postcss-nested to enable nesting of css
postcssPlugins.push(['postcss-import', {}], ['postcss-preset-env', {
features: { 'nesting-rules': true },
}]);
}
//Add plugin which converts URLs in CSS to the correct FULL absolute path
postcssPlugins.push([
postcssUrl({
url: (asset) => {
//TODO: for assets of packages, we want to detect the package path and resolve through node_modules/package-name/...something/assets
if (!asset.url.startsWith('data:')) {
// console.log('Transform CSS URL:'+asset.url);
return `${accessURL}${publicPath}${asset.url}`;
}
return asset.url;
},
}),
]);
if (config.cssMode === 'scss-modules' ||
config.cssMode === 'scss' ||
config.cssMode === 'mixed') {
postcssPlugins = postcssPlugins.concat([
// ['stylelint', {
// 'extends': [
// 'stylelint-config-standard'
// ],
// 'plugins': ['stylelint-scss'],
// 'rules': {
// 'at-rule-no-unknown': null,
// 'scss/at-rule-no-unknown': [
// true,
// {
// ignoreAtRules: [
// 'tailwind',
// 'apply',
// 'variants',
// 'responsive',
// 'screen',
// ],
// },
// ],
// 'no-descending-specificity': null,
// 'at-rule-empty-line-before':null,
// 'rule-empty-line-before': null,
// 'no-missing-end-of-source-newline': null,
// 'max-line-length': null,
// 'color-function-notation': null,
// 'alpha-value-notation': null,
// 'number-max-precision':null,
// },
// }],
isProduction && 'cssnano',
// "postcss-reporter",
]);
}
return {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map',
entry: [
isDevelopment && 'webpack-hot-middleware/client',
path.resolve(process.cwd(), process.env.ENTRY_PATH ||
(process.env.SOURCE_PATH
? process.env.SOURCE_PATH + '/index.tsx'
: './src/index.tsx')),
].filter(Boolean),
watch: isDevelopment || config.analyse,
output: {
path: path.resolve(process.cwd(), process.env.OUTPUT_PATH || './public/bundles'),
filename: '[name].bundle.js',
publicPath: ASSET_PATH,
clean: true,
},
watchOptions: {
//ignore everything except the src folder. ignore specific files in the src folder
ignored: /(^((?!src).)*$|\.d\.ts$|\.js\.map$|\.scss\.json$|node_modules|public|\.idea|[/\\]\..*)/,
aggregateTimeout: 500,
},
devServer: {
client: {
progress: false,
},
},
plugins: [
// new WatchRunPlugin(),
new MiniCssExtractPlugin({
ignoreOrder: true
}),
new webpack.EnvironmentPlugin(Object.keys(process.env)),
isDevelopment && new ReactRefreshWebpackPlugin(),
isDevelopment && new webpack.HotModuleReplacementPlugin(),
config.analyse && new BundleAnalyzerPlugin(),
...(Array.isArray(config.plugins) ? config.plugins : []),
].filter(Boolean),
externals: config.externals || {},
module: {
rules: [
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false,
importLoaders: 1,
modules: {
mode: 'local',
getLocalIdent: getLocalIdent,
auto: (resourcePath) => {
//make sure this only applies to .module.css files, and not to tailwind
return /\.module\.css$/i.test(resourcePath) && !/tailwind/i.test(resourcePath);
}
},
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: postcssPlugins,
},
},
},
],
},
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader?' +
JSON.stringify({
compilerOptions: {
//this is required for dynamic imports & code splitting
module: 'esnext',
moduleResolution: 'node',
sourceMap: isDevelopment,
plugins: [{ 'name': 'typescript-plugin-css-modules' }],
},
}),
options: Object.assign(Object.assign({}, (isDevelopment
? {
getCustomTransformers: () => ({
before: [ReactRefreshTypeScript()],
}),
}
: {})), { transpileOnly: isProduction }),
},
],
exclude: /node_modules/,
},
],
},
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
terserOptions: {
keep_classnames: true,
},
}),
],
},
stats: {
chunks: false,
assets: true,
entrypoints: false,
children: true,
modules: false,
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.css', '.scss', '.json'],
alias: config.alias || {},
},
//Cache is now overwritten in LincdServer based on config, the other value for type would be 'filesystem'
//see also https://webpack.js.org/configuration/other-options/#cache
cache: { type: 'memory' },
};
});
//# sourceMappingURL=config-webpack-app.js.map