From 6420cdd6901b9b66ee88d38784f76df3df82ac3f Mon Sep 17 00:00:00 2001 From: Martin Crawley <82369580+MCrawleyHomeOffice@users.noreply.github.com> Date: Tue, 6 Jul 2021 15:51:29 +0100 Subject: [PATCH] [MAJOR]: Removed deprecated npm-sass and added dart-sass. * Removed npm-sass, added dart-sass, migrated dependecy logic from npm-sass * Fixed a bunch of lint issues in the imported npm-sass code * Fixed issue with sass imports within node_modules folder * Amended fix as per conversation with team, also added extra logging --- helpers/importer.js | 29 ++++++++++++++++++++ helpers/local.js | 35 ++++++++++++++++++++++++ helpers/resolver.js | 16 +++++++++++ helpers/resolver/import.js | 32 ++++++++++++++++++++++ helpers/resolver/nearest-package-root.js | 32 ++++++++++++++++++++++ helpers/resolver/package.js | 29 ++++++++++++++++++++ package.json | 3 +- tasks/sass/index.js | 13 +++++++-- 8 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 helpers/importer.js create mode 100644 helpers/local.js create mode 100644 helpers/resolver.js create mode 100644 helpers/resolver/import.js create mode 100644 helpers/resolver/nearest-package-root.js create mode 100644 helpers/resolver/package.js diff --git a/helpers/importer.js b/helpers/importer.js new file mode 100644 index 0000000..6a1ae66 --- /dev/null +++ b/helpers/importer.js @@ -0,0 +1,29 @@ +const local = require('./local'); +const resolve = require('./resolver'); + +module.exports = function (options) { + const importerOptions = options || {}; + + function importer(url, file, done) { + let importerUrl = url; + if (importerOptions.aliases && importerOptions.aliases[importerUrl]) { + importerUrl = importerOptions.aliases[importerUrl]; + } + + local(importerUrl, file, function (err, isLocal) { + if (err || isLocal) { + done({ file: importerUrl }); + } else { + resolve(importerUrl, file) + .catch(function () { return importerUrl; }) + .then(function (path) { + const importerPath = path.replace(/\.css$/, ''); + return { file: importerPath }; + }) + .then(done); + } + }); + } + + return importer; +}; diff --git a/helpers/local.js b/helpers/local.js new file mode 100644 index 0000000..fa1aee0 --- /dev/null +++ b/helpers/local.js @@ -0,0 +1,35 @@ +const path = require('path'); +const glob = require('glob'); + +module.exports = function (url, file, done) { + // if url starts with . or / then assume it's a local/relative path + if (['.', path.sep].indexOf(url.substr(0, 1)) > -1) { + return done(null, true); + } + + // otherwise construct a glob to match possible relative file paths + const basedir = path.dirname(file); + + const bits = url.split(path.sep); + + let filename = bits.pop(); + const filepath = bits.join(path.sep); + + // support _ prefixing of filenames + if (filename[0] !== '_') { + filename = '?(_)' + filename; + } + + // match .scss, .sass or .css if the extension is not defined + if (filename.indexOf('.') === -1) { + filename += '.@(sa|c|sc)ss'; + } + + const target = path.join(basedir, filepath, filename); + + // test the constructed glob against the file system for possible matches + glob(target, function (err, list) { + done(err, list.length); + }); + return null; +}; diff --git a/helpers/resolver.js b/helpers/resolver.js new file mode 100644 index 0000000..9c42800 --- /dev/null +++ b/helpers/resolver.js @@ -0,0 +1,16 @@ +const Package = require('./resolver/package'); +const Import = require('./resolver/import'); +const nearestPackageRoot = require('./resolver/nearest-package-root'); + +module.exports = function (importUrl, importOriginPath) { + const _import = new Import(importUrl); + + return nearestPackageRoot(_import.packageName(), importOriginPath).then(function (packageRoot) { + const _package = new Package(packageRoot); + + if (_import.isEntrypoint()) { + return _package.fullPathToEntrypoint(); + } + return _package.root(_import.specifiedFilePath()); + }); +}; diff --git a/helpers/resolver/import.js b/helpers/resolver/import.js new file mode 100644 index 0000000..7bb6b0f --- /dev/null +++ b/helpers/resolver/import.js @@ -0,0 +1,32 @@ +const path = require('path'); + +function Import(importUrl) { + this.importUrl = importUrl; +} + +Import.prototype.isScoped = function () { + return this.importUrl[0] === '@'; +}; + +Import.prototype.packageName = function () { + if (this.isScoped()) { + return this.importUrl.split(path.sep, 2).join(path.sep); + } + return this.importUrl.split(path.sep, 1)[0]; +}; + +Import.prototype.isEntrypoint = function () { + const safePathSplitPattern = new RegExp(path.sep + '.'); + const pathSegmentCount = this.importUrl.split(safePathSplitPattern).length; + + if (this.isScoped()) { + return pathSegmentCount === 2; + } + return pathSegmentCount === 1; +}; + +Import.prototype.specifiedFilePath = function () { + return this.importUrl.slice(this.packageName().length); +}; + +module.exports = Import; diff --git a/helpers/resolver/nearest-package-root.js b/helpers/resolver/nearest-package-root.js new file mode 100644 index 0000000..f1ca4f4 --- /dev/null +++ b/helpers/resolver/nearest-package-root.js @@ -0,0 +1,32 @@ +const path = require('path'); +const findup = require('findup'); + +/** + * @param {string} importOriginPath Path of file that made @import reference + */ +module.exports = function (packageName, importOriginPath) { + const pathToFind = path.join('node_modules', packageName, 'package.json'); + const dirnameOfImportOrigin = path.dirname(importOriginPath); + let handleFoundPath; + + const promise = new Promise(function (resolve, reject) { + handleFoundPath = function (err, nearestPackageParent) { + if (err) { + try { + return resolve(path.dirname(require.resolve(`${packageName}/package.json`))); + } catch (e) { + console.log(`Failed to find module ${packageName}/package.json for ${importOriginPath}`, e); + } + return reject(err); + } + + const packageJSONLocation = path.join(nearestPackageParent, 'node_modules', packageName); + + return resolve(packageJSONLocation); + }; + }); + + findup(dirnameOfImportOrigin, pathToFind, handleFoundPath); + + return promise; +}; diff --git a/helpers/resolver/package.js b/helpers/resolver/package.js new file mode 100644 index 0000000..8ab4962 --- /dev/null +++ b/helpers/resolver/package.js @@ -0,0 +1,29 @@ +const path = require('path'); + +function Package(rootPath) { + this.root = path.join.bind(null, rootPath); + this.JSON = require(this.root('package.json')); +} + +Package.prototype.fullPathToEntrypoint = function () { + return this.root(this.entrypoint()); +}; + +Package.prototype.entrypoint = function () { + if (this.JSON.sass) { + return this.JSON.sass; + // look for "style" declaration in package.json + } else if (this.JSON.style) { + return this.JSON.style; + // look for "styles" declaration in package.json + } else if (this.JSON.styles) { + return this.JSON.styles; + // look for a css/sass/scss file in the "main" declaration in package.json + } else if (/\.(sa|c|sc)ss$/.test(this.JSON.main)) { + return this.JSON.main; + // otherwise assume ./styles.scss + } + return 'styles'; +}; + +module.exports = Package; diff --git a/package.json b/package.json index f9d050f..222e02d 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "cp": "^0.2.0", "dotenv": "^4.0.0", "duplexify": "^3.5.0", + "findup": "^0.1.5", "hof-transpiler": "^2.0.2", "lodash.merge": "^4.6.0", "lodash.throttle": "^4.1.1", @@ -35,7 +36,7 @@ "minimatch": "^3.0.3", "minimist": "^1.2.0", "mkdirp": "^0.5.1", - "npm-sass": "^2.2.1", + "sass": "^1.35.1", "uglify-js": "^2.8.22", "witch": "^1.0.0" }, diff --git a/tasks/sass/index.js b/tasks/sass/index.js index 6801a26..7ad9e0b 100644 --- a/tasks/sass/index.js +++ b/tasks/sass/index.js @@ -2,7 +2,8 @@ const fs = require('fs'); const path = require('path'); -const sass = require('npm-sass'); +const sass = require('sass'); +const importer = require('../../helpers/importer'); const mkdir = require('../../lib/mkdir'); @@ -17,9 +18,17 @@ module.exports = config => { .then(() => new Promise((resolve, reject) => { const aliases = {}; if (config.theme) { + console.log('Applying theme: ' + config.theme); aliases.$$theme = `hof-theme-${config.theme}`; } - sass(config.sass.src, { aliases }, (err, result) => err ? reject(err) : resolve(result.css)); + + sass.render({ + file: config.sass.src, + importer: importer({ aliases }), + aliases + }, function (err, result) { + err ? reject(err) : resolve(result.css); + }); })) .then(css => new Promise((resolve, reject) => { fs.writeFile(out, css, err => err ? reject(err) : resolve());