UNPKG

lincd-cli

Version:

Command line tools for the lincd.js library

246 lines 12.7 kB
/// <reference path="colors.d.ts" /> import colors from 'colors'; import * as path from 'path'; import webpack from 'webpack'; import * as process from 'process'; import fs from 'fs'; const { Compilation } = webpack; export default class DeclarationPlugin { constructor(options = {}) { this.logMessages = true; this.exportRoot = '/lib'; this.options = options; this.options['out'] = options.out ? options.out : './builds/declarations.d.ts'; this.options['config'] = options.config ? options.config : process.cwd() + '/daconfig.js'; //var moduleConfig = this.getModuleConfig(); this.options['root'] = options.root || this.exportRoot; //'/lib' this.logMessages = options.debug ? options.debug : false; //load package.json with fs this.modulePackageInfo = JSON.parse(fs.readFileSync(process.cwd() + '/package.json', 'utf8')); // this.modulePackageInfo = await import(process.cwd() + '/package.json'); // this.debug('found package name: '+this.modulePackageInfo.name); } // apply(compiler) { // // Specify the event hook to attach to // compiler.hooks.emit.tapAsync( // 'MyExampleWebpackPlugin', // (compilation, callback) => { // // } apply(compiler) { this.debug('applying '); //when the compiler is ready to emit files // compiler.plugin('emit', (compilation,callback) => compiler.hooks.compilation.tap('DeclarationPlugin', (compilation) => { //NOTE: even though the stage comes from the processAssets hook and not the afterProcessAssets hook // somehow this only works WITH the stage defined compilation.hooks.afterProcessAssets.tap({ stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, name: 'DeclarationPlugin', }, () => { // this.debug('indexing and removing declaration assets'); //collect all generated declaration files //and remove them from the assets that will be emitted //NOTE: at some point we decided to overwrite declaration files between emits because sometimes only one new declaration file is emitted //this may cause issues when you remove a file during the continuous building process, but better than the other way around for now if (!this.declarationFiles) { this.declarationFiles = {}; } compilation.getAssets().forEach((asset, key) => { // this.debug('key '+key.toString()) // this.debug('value '+Object.getOwnPropertyNames(asset).join(", ")) // this.debug('asset: ' + asset.name); if (asset.name.indexOf('.d.ts') !== -1) { if (this.declarationFiles[asset.name]) { this.debug('overwriting ' + asset.name); } this.declarationFiles[asset.name] = asset; this.debug('indexed and removed asset: ' + colors.green(asset.name)); compilation.deleteAsset(asset.name); } }); if (Object.keys(this.declarationFiles).length == 0) { this.debug("Didn't build .d.ts file because no declaration assets were emitted during build process." .yellow); this.debug('This is likely because webpack is using cache.'.yellow); this.debug('In watch mode, declaration assets will be emitted once you change a ts(x) source file' .yellow); // this.log('Make sure to run '.yellow + 'tsc'.blue + ' before running webpack'.yellow); // this.log( // 'Make sure to test for '.yellow + // '/(?!.*.d.ts).ts(x?)$/'.blue.bold['underline'] + // ' in the ts-loader in webpack.config.json'.yellow, // ); // this.log(('Assets: ' + Object.keys(compilation.assets).toString()).yellow); // callback(); return; } //combine them into one declaration file var combinedDeclaration = this.generateCombinedDeclaration(this.declarationFiles); //moduleConfig //and insert that back into the assets // compilation.assets[this.options.out] = { // source: function() { // return combinedDeclaration; // }, // size: function() { // return combinedDeclaration.length; // }, // }; // As suggested by @JonWallsten here: https://github.com/TypeStrong/ts-loader/pull/1251#issuecomment-800032753 compilation.emitAsset(this.options.out, new webpack.sources.RawSource(combinedDeclaration)); //get meta data from module exports /*var metaRdfJson = this.generateMetaRdfJson(compilation,moduleConfig); //and insert that back into the assets as [module_name].meta.json compilation.assets[this.options.out.replace(".d.ts",".meta.rdf.json")] = { source: function() { return metaRdfJson; }, size: function() { return metaRdfJson.length; } };*/ //} }); }); } debug(...msgs) { msgs.unshift('declarations:'); if (this.logMessages) { console.log.apply(null, msgs); } } log(...msgs) { msgs.unshift('declarations:'); console.log.apply(null, msgs); } generateCombinedDeclaration(declarationFiles) { this.debug('generating combined declaration'); var declarations = ''; //this.debug("daCore: using config ",moduleConfig); //this.debug("Combining these files:"+Object.keys(declarationFiles).toString()); //get current directory that webpack is run from (base of project), and replace backward slashes by forward ones to compare var basePath = process.cwd().replace(/\\/g, '/') + '/'; this.debug('Base path:', colors.blue(basePath)); var npmModuleName = this.modulePackageInfo.name; // let moduleName = npmModuleName.replace(/\@\w+\//, ''); var importMap = {}; for (var declarationFileName in declarationFiles) { //this.debug("Parsing "+declarationFileName); var declarationFile = declarationFiles[declarationFileName]; var data = declarationFile.source.source(); if (!data.split) { console.warn(typeof data, declarationFileName + ' - cannot split declaration contents. Not a string?'); continue; } var lines = data.split('\n'); var i = lines.length; while (i--) { var line = lines[i]; //exclude empty lines var excludeLine = line == ''; //if importing something, or re-exporting something if (/import ([a-z0-9A-Z_\-\*\{\}\s,]+)/.test(line) || /export ([a-z0-9A-Z_-\{\}\s,\*]+) from/.test(line)) { var fileImports = line.indexOf('"') !== -1 ? line.match(/\"([^\"]+)\"/) : line.match(/\'([^\']+)\'/); if (fileImports && fileImports.length > 1) { var importPath = fileImports[1]; //if it is importing a relative path and it is a new one if ((importPath.substr(0, 2) == './' || importPath.substr(0, 3) == '../') && !importMap[importPath]) { //we will replace it with the local npm module later, calc and save the absolute path for now //we parse from builds, because now TS-LOADER gives paths relative to its output folder let parsed = path.parse('./builds/' + declarationFileName); let fileDirectory = parsed.dir; var absoluteImportPath = path.resolve(fileDirectory, importPath); // this.debug('declarationfilename '+declarationFileName); // this.debug('filedir '+fileDirectory); this.debug('import ' + colors.blue(importPath), ' -> ' + colors.green(absoluteImportPath)); importMap[importPath] = absoluteImportPath; } } } //exclude re-exports //excludeLine = excludeLine || (/export ([a-z0-9A-Z_-\{\}\s,\*]+) from/).test(line); //exclude unnamed local imports like: import "./some.scss" or import "./someFile"t excludeLine = excludeLine || /import ["'][a-z0-9A-Z_\-.\/\\]+["']/.test(line); //if defined, check for excluded references if (!excludeLine && this.excludedReferences && line.indexOf('<reference') !== -1) { excludeLine = this.excludedReferences.some((reference) => line.indexOf(reference) !== -1); } if (excludeLine) { this.debug('Excluding line ' + i + ': ' + line); lines.splice(i, 1); } else { if (line.indexOf('declare ') !== -1) { lines[i] = line.replace('declare ', ''); } //add tab lines[i] = lines[i]; } } //TS Loader now uses paths relative to output dir. so here we remove a single ../ from the path (which is in there because /builds is the output dir and /lib is the relative path of the file) let fixedDeclarationPath = declarationFileName .replace(/\\/g, '/') .replace('../', '') .replace('.d.ts', ''); var moduleDeclaration = npmModuleName + '/' + fixedDeclarationPath; //this.debug('basePath:'+basePath); declarations += "declare module '" + moduleDeclaration + "' {\n\t" + lines.join('\n\t') + '\n}\n\n'; this.debug('Defining module ' + colors.yellow(moduleDeclaration) + ' from ' + colors.blue(declarationFileName)); } for (var relativeImportPath in importMap) { let absoluteImportPath = importMap[relativeImportPath]; let npmImportModule = npmModuleName + '/' + absoluteImportPath .substr(basePath.length) .replace('.d.ts', '') .replace(/\\/g, '/'); this.debug('Replacing ' + colors.blue(relativeImportPath) + ' with ' + colors.yellow(npmImportModule)); //wrap in quotes to omit problems with replacing partials and having to fix order of replacements declarations = declarations.replace(new RegExp('(\'|")' + relativeImportPath + '(\'|")', 'g'), '"' + npmImportModule + '"'); } let indexModulePath = this.modulePackageInfo.name + this.exportRoot + '/index'; this.debug('Replacing index ' + colors.yellow(indexModulePath) + ' with ' + colors.yellow(this.modulePackageInfo.name)); declarations = declarations.replace("'" + indexModulePath + "'", "'" + this.modulePackageInfo.name + "'"); //replace alias if (this.options.alias) { for (let aliasKey in this.options.alias) { //declarations = declarations.replace(aliasKey,this.options.alias[aliasKey]); this.debug('Replacing alias ' + aliasKey + ' with ' + this.options.alias[aliasKey]); declarations = declarations.replace(new RegExp(aliasKey, 'g'), this.options.alias[aliasKey]); } } return declarations; } } //# sourceMappingURL=declaration-plugin.js.map