UNPKG

lincd-cli

Version:

Command line tools for the lincd.js library

369 lines 17.8 kB
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 ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import chalk from 'chalk'; import fs from 'fs'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import path from 'path'; import ReactRefreshTypeScript from 'react-refresh-typescript'; import TerserPlugin from 'terser-webpack-plugin'; import webpack from 'webpack'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; import { generateScopedName } from './utils.js'; import { LinkedFileStorage } from 'lincd/utils/LinkedFileStorage'; import postcssUrl from 'postcss-url'; 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 = ['tailwind', 'postcss']; 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); return generateScopedName(name, context.resourcePath); } export const getLincdConfig = () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x; const lincdConfigPathJs = path.resolve(process.cwd(), 'lincd.config.js'); const lincdConfigPathJson = path.resolve(process.cwd(), 'lincd.config.json'); //default config let config = { //tailwind is default cssMode: cssModes[0], webpack: { cache: true, analyse: false, }, server: {}, }; // Load from package.json or config files let loaded; if (typeof packageJson.lincdApp === 'object') { loaded = packageJson.lincdApp; } else if (fs.existsSync(lincdConfigPathJs)) { let lincdConfig = yield import(lincdConfigPathJs); loaded = lincdConfig.default; } else if (fs.existsSync(lincdConfigPathJson)) { loaded = JSON.parse(fs.readFileSync(lincdConfigPathJson, 'utf-8')); } // Backward compatibility: migrate flat structure to nested if (loaded) { config.cssMode = (_a = loaded.cssMode) !== null && _a !== void 0 ? _a : config.cssMode; // Move webpack properties config.webpack = { cache: (_d = (_c = (_b = loaded.webpack) === null || _b === void 0 ? void 0 : _b.cache) !== null && _c !== void 0 ? _c : loaded.cacheWebpack) !== null && _d !== void 0 ? _d : true, analyse: (_f = (_e = loaded.webpack) === null || _e === void 0 ? void 0 : _e.analyse) !== null && _f !== void 0 ? _f : loaded.analyse, plugins: (_h = (_g = loaded.webpack) === null || _g === void 0 ? void 0 : _g.plugins) !== null && _h !== void 0 ? _h : loaded.plugins, externals: (_k = (_j = loaded.webpack) === null || _j === void 0 ? void 0 : _j.externals) !== null && _k !== void 0 ? _k : loaded.externals, alias: (_m = (_l = loaded.webpack) === null || _l === void 0 ? void 0 : _l.alias) !== null && _m !== void 0 ? _m : loaded.alias, cssGlobalModulePaths: (_p = (_o = loaded.webpack) === null || _o === void 0 ? void 0 : _o.cssGlobalModulePaths) !== null && _p !== void 0 ? _p : loaded.cssGlobalModulePaths, }; // Move server properties config.server = { multiCore: (_r = (_q = loaded.server) === null || _q === void 0 ? void 0 : _q.multiCore) !== null && _r !== void 0 ? _r : loaded.multiCore, cachePaths: (_t = (_s = loaded.server) === null || _s === void 0 ? void 0 : _s.cachePaths) !== null && _t !== void 0 ? _t : loaded.cachePaths, cacheTimeout: (_v = (_u = loaded.server) === null || _u === void 0 ? void 0 : _u.cacheTimeout) !== null && _v !== void 0 ? _v : loaded.cacheTimeout, loadAppComponent: (_x = (_w = loaded.server) === null || _w === void 0 ? void 0 : _w.loadAppComponent) !== null && _x !== void 0 ? _x : loaded.loadAppComponent, }; } 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* () { var _y, _z, _0, _1, _2, _3; // 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 // for Capacitor apps (APP_ENV is set), use /bundles/ since Capacitor's webDir strips /public (see: capacitor.config.ts) // for web server builds, use /public/bundles/ to match express.static() const isCapacitorBuild = process.env.APP_ENV !== undefined; const publicPath = isCapacitorBuild ? '' : '/public'; const bundlesPath = publicPath + '/bundles/'; // ASSET_PATH is used load the assets from the correct path // if ASSET_PATH is set in environment (app builds), use it directly // otherwise, use CDN URL + bundlesPath for production, or bundlesPath for development const ASSET_PATH = process.env.ASSET_PATH || (accessURL ? accessURL + bundlesPath : bundlesPath); let config = yield getLincdConfig(); let postcssPlugins = []; if (config.cssMode === 'tailwind') { // Tailwind v4 is configured via CSS directives (@config, @import, @theme) // See theme.css for the main configuration postcssPlugins.push(['@tailwindcss/postcss', {}]); } else { // postcss mode: use postcss-nested to enable nesting of css + CSS Modules postcssPlugins.push(['postcss-import', {}], [ 'postcss-preset-env', { features: { 'nesting-rules': true }, }, ]); } /** * Rewrite CSS asset URLs for the web bundle while keeping native (Capacitor) paths intact. * * Examples that will now work transparently in CSS: * url('/images/foo.svg') -> web: https://cdn/app/public/images/foo.svg * -> native: /images/foo.svg (served from bundled webDir) * url('./images/bar.png') -> web: https://cdn/app/public/images/bar.png * url('/public/images/baz.png') -> web: https://cdn/app/public/images/baz.png * * Data-URIs and absolute http(s) URLs pass through untouched. */ postcssPlugins.push([ postcssUrl({ url: ({ url }) => { if (!url || url.startsWith('data:')) { return url; } // remove wrapping quotes added by postcss let cleanedUrl = url.replace(/^['"]|['"]$/g, '').trim(); // skip absolute http(s) references if (/^https?:\/\//i.test(cleanedUrl)) { return cleanedUrl; } // remove leading ./ to normalise relative imports if (cleanedUrl.startsWith('./')) { cleanedUrl = cleanedUrl.slice(1); } // ensure a single leading slash if (!cleanedUrl.startsWith('/')) { cleanedUrl = `/${cleanedUrl}`; } // collapse optional /public prefix - CSS can freely use /images/... now if (cleanedUrl.startsWith('/public/')) { cleanedUrl = cleanedUrl.replace('/public/', '/'); } // For Capacitor builds we keep the relative path (assets are bundled locally) if (isCapacitorBuild) { return cleanedUrl; } const baseUrl = (accessURL || '').replace(/\/$/, ''); return `${baseUrl}${publicPath}${cleanedUrl}`; }, }), ]); 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 || ((_y = config.webpack) === null || _y === void 0 ? void 0 : _y.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$|\.css\.json$|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(), ((_z = config.webpack) === null || _z === void 0 ? void 0 : _z.analyse) && new BundleAnalyzerPlugin(), new WebpackManifestPlugin({ fileName: 'manifest.json', publicPath: ASSET_PATH, writeToFileEmit: true, filter: (file) => /\.(js|css)$/i.test(file.name), }), ...(Array.isArray((_0 = config.webpack) === null || _0 === void 0 ? void 0 : _0.plugins) ? config.webpack.plugins : []), ].filter(Boolean), externals: ((_1 = config.webpack) === null || _1 === void 0 ? void 0 : _1.externals) || {}, module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { url: false, importLoaders: 2, 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, }, }), ], splitChunks: { cacheGroups: { // Prevent CSS from being split into separate shared chunks // Keep all CSS for a lazy-loaded route together in one file // This prevents FOUC when lazy routes need multiple CSS chunks defaultVendors: false, default: false, }, }, }, stats: { chunks: false, assets: true, entrypoints: false, children: true, modules: false, }, resolve: { extensions: ['.tsx', '.ts', '.js', '.css', '.json'], alias: ((_2 = config.webpack) === null || _2 === void 0 ? void 0 : _2.alias) || {}, extensionAlias: { '.js': ['.tsx', '.ts', '.js'], '.jsx': ['.tsx', '.jsx'], }, // traceResolution: true }, //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: ((_3 = config.webpack) === null || _3 === void 0 ? void 0 : _3.cache) ? { type: 'filesystem' } : { type: 'memory' }, }; }); //# sourceMappingURL=config-webpack-app.js.map