lincd-cli
Version:
Command line tools for the lincd.js library
394 lines • 19.3 kB
JavaScript
;
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