diff --git a/packages/sui-js-compiler/package.json b/packages/sui-js-compiler/package.json index 350baf211..0377a96c2 100644 --- a/packages/sui-js-compiler/package.json +++ b/packages/sui-js-compiler/package.json @@ -3,7 +3,10 @@ "version": "1.21.0", "description": "JavaScript Compiler", "type": "module", - "exports": "./src/index.js", + "exports": { + ".": "./index.js", + "./swc-config.js": "./swc-config.js" + }, "bin": { "sui-js-compiler": "./index.js" }, diff --git a/packages/sui-studio/bin/helpers/checkDepth.js b/packages/sui-studio/bin/helpers/checkDepth.js new file mode 100644 index 000000000..868cba8cb --- /dev/null +++ b/packages/sui-studio/bin/helpers/checkDepth.js @@ -0,0 +1,15 @@ +'use strict' + +const path = require('path') + +/** + * Check depth + * @param {string} filePath - path of the file + * @param {number} up - number of directories to go up + * @returns {boolean} + */ +module.exports = (filePath, up) => { + // components/atom/button + const depth = path.normalize(filePath).split(path.sep).length - 1 + return depth >= up +} diff --git a/packages/sui-studio/bin/helpers/copy.js b/packages/sui-studio/bin/helpers/copy.js index 5de175ba8..e383a43d7 100644 --- a/packages/sui-studio/bin/helpers/copy.js +++ b/packages/sui-studio/bin/helpers/copy.js @@ -6,49 +6,8 @@ const path = require('path') const fs = require('fs-extra') const glob = require('fast-glob') -const {DEBUG} = process.env - -/** - * Show logging information if debug mode is enabled - * @param {...any} args - arguments to log - * @returns {void} - */ -const debug = (...args) => { - DEBUG && console.log('[copyfiles] ', ...args) -} - -/** - * Check depth - * @param {string} filePath - path of the file - * @param {number} up - number of directories to go up - * @returns {boolean} - */ -const checkDepth = (filePath, up) => { - // components/atom/button - const depth = path.normalize(filePath).split(path.sep).length - 1 - return depth >= up -} - -/** - * Resolve file path - * @param {string} filePath - path of the file - * @param {object} config - configuration object - * @param {object} config.flatten - flatten the path - * @param {number} config.up - number of directories to go up - * @returns {string} - */ -const resolveFilePath = (filePath, {flatten, up}) => { - if (flatten === true) return path.basename(filePath) - if (up === 0) return filePath - - if (!checkDepth(filePath, up)) { - throw new Error( - "The number of folders you're trying to go up are not correct. Check the path or the up config" - ) - } - - return path.join(...path.normalize(filePath).split(path.sep).slice(up)) -} +const debug = require('./debug.js') +const resolveFilePath = require('./resolveFilePath.js') module.exports = async function copyFiles(args, config = {}) { const input = args.slice() diff --git a/packages/sui-studio/bin/helpers/copyStaticFiles.js b/packages/sui-studio/bin/helpers/copyStaticFiles.js index 2bfa54484..c9b7644f7 100644 --- a/packages/sui-studio/bin/helpers/copyStaticFiles.js +++ b/packages/sui-studio/bin/helpers/copyStaticFiles.js @@ -10,7 +10,7 @@ module.exports = function copyStaticFiles() { 'components/README.md', 'components/*/*/README.md', 'components/*/*/CHANGELOG.md', - 'components/*/*/src/index.js', + 'components/*/*/src/**/*.js', 'components/*/*/demo/playground', DESTINATION_FOLDER ], diff --git a/packages/sui-studio/bin/helpers/debug.js b/packages/sui-studio/bin/helpers/debug.js new file mode 100644 index 000000000..1a7602c36 --- /dev/null +++ b/packages/sui-studio/bin/helpers/debug.js @@ -0,0 +1,11 @@ +const {DEBUG} = process.env + +/** + * Show logging information if debug mode is enabled + * @param {...any} args - arguments to log + * @returns {void} + */ + +module.exports = function (...args) { + DEBUG && console.log('[copyfiles] ', ...args) +} diff --git a/packages/sui-studio/bin/helpers/findExportedExpressions.js b/packages/sui-studio/bin/helpers/findExportedExpressions.js new file mode 100644 index 000000000..2587a7643 --- /dev/null +++ b/packages/sui-studio/bin/helpers/findExportedExpressions.js @@ -0,0 +1,155 @@ +const {parseSync, transformFileSync} = require('@swc/core') +const path = require('node:path') + +function findExportedExpressions( + file, + {isDefault = true, variableName, swc: swcSettings} = {} +) { + const {dir, base = 'index.js'} = path.parse(`${file}`) + + const {code: transformedCode} = transformFileSync(`${file}`, swcSettings) + + const AST = parseSync(transformedCode) + + if (typeof AST !== 'object') { + throw new Error('invalid file data') + } + const {body} = AST + const globalDeclarations = body.reduce( + ( + acc, + { + type, + source, + id, + declaration = {}, + declarations = [], + specifiers = [], + identifier = {} + } + ) => { + if (type === 'ExportNamedDeclaration' && source !== null) { + const {value, type} = source + if (type === 'StringLiteral') { + specifiers?.forEach(({type, orig, exported}) => { + if (['ExportDefaultSpecifier', 'ExportSpecifier'].includes(type)) { + acc = { + ...acc, + [exported?.value || orig?.value]: { + localName: orig?.value, + currentName: exported?.value || orig?.value, + source: + path.relative(process.cwd(), path.resolve(dir, value)) || + `${dir}/${base}` + } + } + } + }) + } + } + if (type === 'ExportDeclaration') { + const {declarations = []} = declaration + declarations.forEach(({id: {value}}) => { + acc = { + ...acc, + [value]: {localName: value, source: `${dir}/${base}`} + } + }) + } + if (type === 'VariableDeclaration') { + declarations.forEach(({id: {value}} = {id: {}}) => { + acc = {...acc, [value]: {localName: value, source: `${dir}/${base}`}} + }) + } + if (['FunctionDeclaration', 'ClassDeclaration'].includes(type)) { + const {value} = identifier + acc = {...acc, [value]: {localName: value, source: `${dir}/${base}`}} + } + if (type === 'ImportDeclaration' && source !== null) { + const {value, type} = source + if (type === 'StringLiteral') { + specifiers?.forEach(({type, local, imported}, i) => { + if (['ImportDefaultSpecifier', 'ImportSpecifier'].includes(type)) { + acc = { + ...acc, + [imported?.value || local?.value]: { + localName: local?.value, + importedName: imported?.value || local?.value, + source: + path.relative(process.cwd(), path.resolve(dir, value)) || + `${dir}/${base}` + } + } + } + }) + } + } + return acc + }, + {} + ) + + let result = body.find(({type, specifiers}) => { + if (type === 'ExportDefaultDeclaration' && isDefault) { + return true + } + if (type === 'ExportNamedDeclaration') { + specifiers?.forEach(({type, orig, exported}) => { + if (type === 'ExportSpecifier') { + const currentName = exported?.value || orig?.value + if (variableName === currentName) { + return true + } + } + }) + } + if ( + type === 'ExportDefaultExpression' && + (variableName === undefined || isDefault) + ) { + return true + } + }) + result = (({type, expression, specifiers}) => { + if (type === 'ExportDefaultDeclaration' && isDefault) { + return {route: path.resolve(dir, base), found: true, isDefault: true} + } else if (type === 'ExportNamedDeclaration') { + specifiers?.forEach(({type, orig, exported}) => { + if (type === 'ExportSpecifier') { + const currentName = exported?.value || orig?.value + if (variableName === currentName) { + return { + isDefault: false, + route: globalDeclarations[currentName].source, + found: globalDeclarations[currentName].source === file, + variableName: globalDeclarations[currentName].localName + } + } + } + }) + } else if ( + type === 'ExportDefaultExpression' && + variableName === undefined + ) { + const {value} = expression + const {source, localName} = {...(globalDeclarations[value] || {})} + return { + route: source, + found: source === file, + variableName: localName + } + } + })(result) + if (!result) { + result = {route: path.resolve(dir, base), found: false, isDefault: false} + } + if (!result.found && file !== result.route) { + result = findExportedExpressions(result.route, { + isDefault: result.isDefault, + variableName: result.variableName + }) + } + return result +} + +module.exports = findExportedExpressions diff --git a/packages/sui-studio/bin/helpers/generateApiDocs.js b/packages/sui-studio/bin/helpers/generateApiDocs.js index e7028263b..2bf34f0a0 100644 --- a/packages/sui-studio/bin/helpers/generateApiDocs.js +++ b/packages/sui-studio/bin/helpers/generateApiDocs.js @@ -2,15 +2,27 @@ const path = require('node:path') const fg = require('fast-glob') const fs = require('fs-extra') const reactDocs = require('react-docgen') +const findExportedExpressions = require('./findExportedExpressions.js') -module.exports = function generateApiDocs() { +module.exports = async function generateApiDocs() { console.log('[sui-studio] Generating API documentation for components...') console.time('[sui-studio] API generation took') + const {default: swcConfig} = await import('@s-ui/js-compiler/swc-config.js') + const components = fg.sync('components/*/*/src/index.js', {deep: 4}) components.forEach(file => { - const source = fs.readFileSync(file, 'utf-8') + let docFilePath = file + const {dir} = path.parse(file) + const gen = findExportedExpressions(file, {swc: swcConfig}) + const {route, found} = gen + if (found) { + docFilePath = route.split().join('') + } + + const source = fs.readFileSync(docFilePath, 'utf-8') + let docs = {} try { @@ -22,7 +34,7 @@ module.exports = function generateApiDocs() { console.warn(`[sui-studio] Couldn't generate API docs for ${file}`) } - const outputFile = file.replace('index.js', 'definitions.json') + const outputFile = dir.replace('src', '') + 'definitions.json' fs.writeFileSync( path.resolve(process.cwd(), `public/${outputFile}`), JSON.stringify(docs, null, 2) diff --git a/packages/sui-studio/bin/helpers/remove.js b/packages/sui-studio/bin/helpers/remove.js new file mode 100644 index 000000000..6c0dd1422 --- /dev/null +++ b/packages/sui-studio/bin/helpers/remove.js @@ -0,0 +1,41 @@ +// @ts-check + +'use strict' + +const path = require('path') +const fs = require('fs-extra') +const glob = require('fast-glob') + +const debug = require('./debug.js') +const resolveFilePath = require('./resolveFilePath.js') + +module.exports = async function removeFiles(args, config = {}) { + const input = args.slice() + const outDir = input.pop() + const globOpts = {} + + const {flatten = false, up = 0} = config + + if (config.exclude) globOpts.ignore = config.exclude + if (config.all) globOpts.dot = true + if (config.follow) globOpts.followSymbolicLinks = true + + debug(`Config for glob: `, globOpts) + + const files = await glob(input, globOpts) + + if (files.length === 0) { + debug('No files found.') + return + } + + debug(`Removing ${files.length} files from ${input} `) + + return Promise.all( + files.map(file => { + const outName = path.join(outDir, resolveFilePath(file, {flatten, up})) + debug(`Removing ${outName}`) + return fs.unlinkSync(outName) + }) + ) +} diff --git a/packages/sui-studio/bin/helpers/removeStaticFiles.js b/packages/sui-studio/bin/helpers/removeStaticFiles.js new file mode 100644 index 000000000..73f08c3b3 --- /dev/null +++ b/packages/sui-studio/bin/helpers/removeStaticFiles.js @@ -0,0 +1,14 @@ +const remove = require('./remove.js') + +const DESTINATION_FOLDER = 'public' + +module.exports = function removeStaticFiles() { + console.log('[sui-studio] Removing unnecessary static files...') + console.time('[sui-studio] Removing static files took') + + return remove(['components/*/*/src/**/*.js', DESTINATION_FOLDER], { + exclude: 'node_modules/**' + }).then(() => { + console.timeEnd('[sui-studio] Removing static files took') + }) +} diff --git a/packages/sui-studio/bin/helpers/resolveFilePath.js b/packages/sui-studio/bin/helpers/resolveFilePath.js new file mode 100644 index 000000000..6c7dd989c --- /dev/null +++ b/packages/sui-studio/bin/helpers/resolveFilePath.js @@ -0,0 +1,25 @@ +'use strict' + +const path = require('path') +const checkDepth = require('./checkDepth.js') + +/** + * Resolve file path + * @param {string} filePath - path of the file + * @param {object} config - configuration object + * @param {object} config.flatten - flatten the path + * @param {number} config.up - number of directories to go up + * @returns {string} + */ +module.exports = (filePath, {flatten, up}) => { + if (flatten === true) return path.basename(filePath) + if (up === 0) return filePath + + if (!checkDepth(filePath, up)) { + throw new Error( + "The number of folders you're trying to go up are not correct. Check the path or the up config" + ) + } + + return path.join(...path.normalize(filePath).split(path.sep).slice(up)) +} diff --git a/packages/sui-studio/bin/sui-studio-build.js b/packages/sui-studio/bin/sui-studio-build.js index 06430990b..8bf2a275a 100755 --- a/packages/sui-studio/bin/sui-studio-build.js +++ b/packages/sui-studio/bin/sui-studio-build.js @@ -9,6 +9,7 @@ const program = require('commander') const {NO_COMPONENTS_MESSAGE} = require('../config/index.js') const generateApiDocs = require('./helpers/generateApiDocs.js') const copyStaticFiles = require('./helpers/copyStaticFiles.js') +const removeStaticFiles = require('./helpers/removeStaticFiles.js') const copyGlobals = require('./helpers/copyGlobals.js') program @@ -59,6 +60,7 @@ if (needsBuild) { .then(copyStaticFiles) .then(copyGlobals) .then(generateApiDocs) + .then(removeStaticFiles) .then(() => process.exit(0)) .catch(() => process.exit(1)) } diff --git a/packages/sui-studio/bin/sui-studio.js b/packages/sui-studio/bin/sui-studio.js index 75c4309cb..19fc0f1b0 100755 --- a/packages/sui-studio/bin/sui-studio.js +++ b/packages/sui-studio/bin/sui-studio.js @@ -5,6 +5,7 @@ const path = require('path') const {getSpawnPromise} = require('@s-ui/helpers/cli') const {version} = require('../package.json') +const generateApiDocs = require('./helpers/generateApiDocs.js') const copyStaticFiles = require('./helpers/copyStaticFiles.js') const copyGlobals = require('./helpers/copyGlobals.js') @@ -18,6 +19,7 @@ program await copyGlobals() await copyStaticFiles() + await generateApiDocs() const devServerExec = require.resolve('@s-ui/bundler/bin/sui-bundler-dev') const args = ['-c', path.join(__dirname, '..', 'src')] diff --git a/packages/sui-studio/package.json b/packages/sui-studio/package.json index 2de03914b..8b286d9a1 100644 --- a/packages/sui-studio/package.json +++ b/packages/sui-studio/package.json @@ -13,6 +13,7 @@ "@babel/cli": "7", "@s-ui/bundler": "9", "@s-ui/helpers": "1", + "@s-ui/js-compiler": "1", "@s-ui/react-context": "1", "@s-ui/react-router": "1", "@s-ui/test": "8", diff --git a/packages/sui-studio/src/components/tryRequire.js b/packages/sui-studio/src/components/tryRequire.js index b18d0e0cd..d521986e6 100644 --- a/packages/sui-studio/src/components/tryRequire.js +++ b/packages/sui-studio/src/components/tryRequire.js @@ -1,6 +1,6 @@ /* global __BASE_DIR__ */ -import {safeImport} from './utils' +import {safeImport} from './utils.js' const fetchStaticFile = path => window @@ -16,7 +16,7 @@ export const fetchComponentSrcRawFile = ({category, component}) => export const fetchComponentsDefinitions = ({category, component}) => window - .fetch(`/components/${category}/${component}/src/definitions.json`) + .fetch(`/components/${category}/${component}/definitions.json`) .then(res => res.json()) export const fetchComponentsReadme = () =>