UNPKG

lincd-cli

Version:

Command line tools for the lincd.js library

394 lines 19.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getWebpackAppConfig = exports.getLincdConfig = void 0; const react_refresh_webpack_plugin_1 = __importDefault(require("@pmmmwh/react-refresh-webpack-plugin")); const chalk_1 = __importDefault(require("chalk")); const fs_1 = __importDefault(require("fs")); const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin")); const path_1 = __importDefault(require("path")); const react_refresh_typescript_1 = __importDefault(require("react-refresh-typescript")); const terser_webpack_plugin_1 = __importDefault(require("terser-webpack-plugin")); const webpack_1 = __importDefault(require("webpack")); const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer"); const webpack_manifest_plugin_1 = require("webpack-manifest-plugin"); const utils_js_1 = require("./utils.js"); const LinkedFileStorage_1 = require("lincd/utils/LinkedFileStorage"); const postcss_url_1 = __importDefault(require("postcss-url")); const isProduction = process.env.NODE_ENV === 'production'; const isDevelopment = process.env.NODE_ENV === 'development'; const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.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_1.default.magenta("Changed files sorted by 'modified time' stamps:")); const entriesToCheck = []; changedFiles.forEach((file) => { try { const stat = fs_1.default.statSync(file.toString()); console.log(` ${chalk_1.default.magenta(file)}`); entriesToCheck.push({ path: file, mtime: stat.mtime }); if (stat.isDirectory()) { const contents = fs_1.default.readdirSync(file.toString()); contents.forEach((name) => { const fullPath = path_1.default.join(file.toString(), name); try { const innerStat = fs_1.default.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 (0, utils_js_1.generateScopedName)(name, context.resourcePath); } const getLincdConfig = async () => { 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_1.default.resolve(process.cwd(), 'lincd.config.js'); const lincdConfigPathJson = path_1.default.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_1.default.existsSync(lincdConfigPathJs)) { let lincdConfig = await Promise.resolve(`${lincdConfigPathJs}`).then(s => __importStar(require(s))); loaded = lincdConfig.default; } else if (fs_1.default.existsSync(lincdConfigPathJson)) { loaded = JSON.parse(fs_1.default.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; }; exports.getLincdConfig = getLincdConfig; const getWebpackAppConfig = async () => { var _a, _b, _c, _d, _e, _f; // set up the storage config for the app await Promise.resolve(`${path_1.default.join(process.cwd(), 'scripts', 'storage-config.js')}`).then(s => __importStar(require(s))); const accessURL = LinkedFileStorage_1.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 = await (0, exports.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([ (0, postcss_url_1.default)({ 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_1.default.resolve(process.cwd(), process.env.ENTRY_PATH || (process.env.SOURCE_PATH ? process.env.SOURCE_PATH + '/index.tsx' : './src/index.tsx')), ].filter(Boolean), watch: isDevelopment || ((_a = config.webpack) === null || _a === void 0 ? void 0 : _a.analyse), output: { path: path_1.default.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 mini_css_extract_plugin_1.default({ ignoreOrder: true, }), new webpack_1.default.EnvironmentPlugin(Object.keys(process.env)), isDevelopment && new react_refresh_webpack_plugin_1.default(), isDevelopment && new webpack_1.default.HotModuleReplacementPlugin(), ((_b = config.webpack) === null || _b === void 0 ? void 0 : _b.analyse) && new webpack_bundle_analyzer_1.BundleAnalyzerPlugin(), new webpack_manifest_plugin_1.WebpackManifestPlugin({ fileName: 'manifest.json', publicPath: ASSET_PATH, writeToFileEmit: true, filter: (file) => /\.(js|css)$/i.test(file.name), }), ...(Array.isArray((_c = config.webpack) === null || _c === void 0 ? void 0 : _c.plugins) ? config.webpack.plugins : []), ].filter(Boolean), externals: ((_d = config.webpack) === null || _d === void 0 ? void 0 : _d.externals) || {}, module: { rules: [ { test: /\.css$/, use: [ mini_css_extract_plugin_1.default.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: { ...(isDevelopment ? { getCustomTransformers: () => ({ before: [(0, react_refresh_typescript_1.default)()], }), } : {}), transpileOnly: isProduction, }, }, ], exclude: /node_modules/, }, ], }, optimization: { minimize: isProduction, minimizer: [ new terser_webpack_plugin_1.default({ 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: ((_e = config.webpack) === null || _e === void 0 ? void 0 : _e.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: ((_f = config.webpack) === null || _f === void 0 ? void 0 : _f.cache) ? { type: 'filesystem' } : { type: 'memory' }, }; }; exports.getWebpackAppConfig = getWebpackAppConfig; //# sourceMappingURL=config-webpack-app.js.map