UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

359 lines (312 loc) 9.8 kB
/* ************************************************************************ * * qooxdoo-compiler - node.js based replacement for the Qooxdoo python * toolchain * * https://github.com/qooxdoo/qooxdoo * * Copyright: * 2011-2019 Zenesis Limited, http://www.zenesis.com * * License: * MIT: https://opensource.org/licenses/MIT * * This software is provided under the same licensing terms as Qooxdoo, * please see the LICENSE file in the Qooxdoo project's top-level directory * for details. * * Authors: * * John Spackman (john.spackman@zenesis.com, @johnspackman) * * *********************************************************************** */ /* eslint-disable @qooxdoo/qx/no-illegal-private-usage */ const fs = qx.tool.utils.Promisify.fs; const path = require("upath"); /** * @external(qx/tool/loadsass.js) * @ignore(loadSass) */ /* global loadSass */ const sass = loadSass(); /** * @ignore(process) */ qx.Class.define("qx.tool.compiler.resources.ScssFile", { extend: qx.core.Object, construct(target, library, filename) { super(); this.__library = library; this.__filename = filename; this.__target = target; this.__sourceFiles = {}; this.__importAs = {}; }, properties: { file: { nullable: false, check: "String", event: "changeFile" }, themeFile: { init: false, check: "Boolean" } }, members: { __library: null, __filename: null, __outputDir: null, __absLocations: null, __sourceFiles: null, __importAs: null, /** * Compiles the SCSS, returns a list of files that were imported) * * @param outputFilename {String} output * @return {String[]} dependent files */ async compile(outputFilename) { this.__outputDir = path.dirname(outputFilename); this.__absLocations = {}; let inputFileData = await this.loadSource( this.__filename, this.__library ); await new qx.Promise((resolve, reject) => { sass.render( { // Always have file so that the source map knows the name of the original file: this.__filename, // data provides the contents, `file` is only used for the sourcemap filename data: inputFileData, outputStyle: "compressed", sourceMap: true, outFile: path.basename(outputFilename), /* * Importer */ importer: (url, prev, done) => { let contents = this.__sourceFiles[url]; if (!contents) { let tmp = this.__importAs[url]; if (tmp) { contents = this.__sourceFiles[tmp]; } } return contents ? { contents } : null; }, functions: { "qooxdooUrl($filename, $url)": ($filename, $url, done) => this.__qooxdooUrlImpl($filename, $url, done) } }, (error, result) => { if (error) { qx.tool.compiler.Console.error( "Error status " + error.status + " in " + this.__filename + "[" + error.line + "," + error.column + "]: " + error.message ); resolve(error); // NOT reject return; } fs.writeFileAsync(outputFilename, result.css.toString(), "utf8") .then(() => fs.writeFileAsync( outputFilename + ".map", result.map.toString(), "utf8" ) ) .then(() => resolve(null)) .catch(reject); } ); }); return Object.keys(this.__sourceFiles); }, _analyseFilename(url, currentFilename) { var m = url.match(/^([a-z0-9_.]+):(\/?[^\/].*)/); if (m) { return { namespace: m[1], filename: m[2], externalUrl: null }; } // It's a real URL like http://abc.com/.. if (url.match(/^[a-z0-9_]+:\/\//)) { return { externalUrl: url }; } // It's either absolute to the website (i.e. begins with a slash) or it's // relative to the current file if (url[0] == "/") { return { namespace: null, filename: url }; } // Must be relative to current file let dir = path.dirname(currentFilename); let filename = path.resolve(dir, url); let library = this.__target .getAnalyser() .getLibraries() .find(library => filename.startsWith(path.resolve(library.getRootDir())) ); if (!library) { qx.tool.compiler.Console.error( "Cannot find library for " + url + " in " + currentFilename ); return null; } let libResourceDir = path.join( library.getRootDir(), this.isThemeFile() ? library.getThemePath() : library.getResourcePath() ); return { namespace: library.getNamespace(), filename: path.relative(libResourceDir, filename), externalUrl: null }; }, reloadSource(filename) { filename = path.resolve(filename); delete this.__sourceFiles[filename]; return this.loadSource(filename); }, async loadSource(filename, library) { filename = path.relative( process.cwd(), path.resolve( this.isThemeFile() ? library.getThemeFilename(filename) : library.getResourceFilename(filename) ) ); let absFilename = filename; if (path.extname(absFilename) == "") { absFilename += ".scss"; } let exists = fs.existsSync(absFilename); if (!exists) { let name = path.basename(absFilename); if (name[0] != "_") { let tmp = path.join(path.dirname(absFilename), "_" + name); exists = fs.existsSync(tmp); if (exists) { absFilename = tmp; } } } if (!exists) { this.__sourceFiles[absFilename] = null; return null; } if (this.__sourceFiles[absFilename] !== undefined) { return qx.Promise.resolve(this.__sourceFiles[absFilename]); } let contents = await fs.readFileAsync(absFilename, "utf8"); let promises = []; contents = contents.replace( /@import\s+["']([^;]+)["']/gi, (match, p1, offset) => { let pathInfo = this._analyseFilename(p1, absFilename); if (pathInfo.externalUrl) { return '@import "' + pathInfo.externalUrl + '"'; } let newLibrary = this.__target .getAnalyser() .findLibrary(pathInfo.namespace); if (!newLibrary) { qx.tool.compiler.Console.error( "Cannot find file to import, url=" + p1 + " in file " + absFilename ); return null; } promises.push(this.loadSource(pathInfo.filename, newLibrary)); let filename = this.isThemeFile() ? newLibrary.getThemeFilename(pathInfo.filename) : newLibrary.getResourceFilename(pathInfo.filename); return '@import "' + path.relative(process.cwd(), filename) + '"'; } ); contents = contents.replace( /\burl\s*\(\s*([^\)]+)*\)/gi, (match, url) => { let c = url[0]; if (c === "'" || c === '"') { url = url.substring(1); } c = url[url.length - 1]; if (c === "'" || c === '"') { url = url.substring(0, url.length - 1); } //return `qooxdooUrl("${filename}", "${url}")`; let pathInfo = this._analyseFilename(url, filename); if (pathInfo) { if (pathInfo.externalUrl) { return `url("${pathInfo.externalUrl}")`; } if (pathInfo.namespace) { let targetFile = path.relative( process.cwd(), path.join( this.__target.getOutputDir(), "resource", pathInfo.filename ) ); let relative = path.relative(this.__outputDir, targetFile); return `url("${relative}")`; } } return `url("${url}")`; } ); this.__sourceFiles[absFilename] = contents; this.__importAs[filename] = absFilename; await qx.Promise.all(promises); return contents; }, getSourceFilenames() { return Object.keys(this.__sourceFiles); }, __qooxdooUrlImpl($filename, $url, done) { let currentFilename = $filename.getValue(); let url = $url.getValue(); let pathInfo = this._analyseFilename(url, currentFilename); if (pathInfo) { if (pathInfo.externalUrl) { return sass.types.String("url(" + pathInfo.externalUrl + ")"); } if (pathInfo.namespace) { let targetFile = path.relative( process.cwd(), path.join( this.__target.getOutputDir(), "resource", pathInfo.filename ) ); let relative = path.relative(this.__outputDir, targetFile); return sass.types.String("url(" + relative + ")"); } } return sass.types.String("url(" + url + ")"); } } });