UNPKG

babel-plugin-transform-scss

Version:

Babel plugin for transpile scss or sass imports to css and embedding them in the head of the document

231 lines (219 loc) 8.14 kB
function getDefaultSassImplementation() { let sassImplPkg = "sass"; try { require.resolve("sass"); } catch (error) { try { require.resolve("node-sass"); sassImplPkg = "node-sass"; } catch (ignoreError) { sassImplPkg = "sass"; } } // eslint-disable-next-line import/no-dynamic-require, global-require return require(sassImplPkg); } const {resolve, dirname, join} = require("path"); const {v4} = require('uuid') const {renderSync} = getDefaultSassImplementation(); const regexps = { sassFile: /([._\-A-Za-z0-9]+)\.s[ac]ss/g, sassExt: /\.s[ac]ss$/, currentFile: /([A-Za-z0-9]+)\.(t|j)s(x)?/g, currentFileExt: /\.(t|j)s(x)?/ }; const isImportDefaultSpecifier = (node) => node.type === 'ImportDefaultSpecifier'; function transformScss(babel) { const {types: t} = babel; return { name: "babel-plugin-transform-scss", visitor: { ImportDeclaration(path, state) { /** * Check if file contains scss | sass extension */ if (!regexps.sassExt.test(path.node.source.value)) return; const sassFileNameMatch = path.node.source.value.match( regexps.sassFile ); /** * Get import sass file name and current file name */ const sassFileName = sassFileNameMatch[0].replace(regexps.sassExt, ""); const file = this.filename.match(regexps.currentFile); const filename = `${file[0].replace( regexps.currentFileExt, "" )}_${sassFileName}_${v4()}`; /** * * Get full path of scss file and transpile content */ const scssFileDirectory = resolve(dirname(state.file.opts.filename)); const fullScssFilePath = join( scssFileDirectory, path.node.source.value ); const projectRoot = process.env.PWD; const nodeModulesPath = join(projectRoot, "node_modules"); const sassDefaults = { file: fullScssFilePath, sourceMap: false, includePaths: [nodeModulesPath, scssFileDirectory, projectRoot, ...(state.opts.include || [])], }; const sassResult = renderSync({...sassDefaults, ...state.opts}); const transpiledContent = sassResult.css.toString() || ""; /** * Implement transpiledContent. Creates replacement for "import 'style.scss'" to JS function * which renders parsed styles to <style> tag */ const defaultImportNode = path.node.specifiers.find( isImportDefaultSpecifier ); if (!defaultImportNode) { path.replaceWith( t.callExpression( t.functionExpression( t.identifier(""), [], t.blockStatement( [ t.ifStatement( t.binaryExpression( "===", t.unaryExpression( "typeof", t.identifier("document") ), t.stringLiteral("undefined") ), t.blockStatement([ t.returnStatement() ]) ), t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("styles"), t.stringLiteral(transpiledContent) ) ]), t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("fileName"), t.stringLiteral(filename) ) ]), t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("element"), t.callExpression( t.memberExpression( t.identifier("document"), t.identifier("querySelector") ), [ t.stringLiteral( `style[data-sass-component='${filename}']` ) ] ) ) ]), t.ifStatement( t.unaryExpression("!", t.identifier("element"), true), t.blockStatement( [ t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("styleBlock"), t.callExpression( t.memberExpression( t.identifier("document"), t.identifier("createElement") ), [t.stringLiteral("style")] ) ) ]), t.expressionStatement( t.assignmentExpression( "=", t.memberExpression( t.identifier("styleBlock"), t.identifier("innerHTML") ), t.identifier("styles") ) ), t.expressionStatement( t.callExpression( t.memberExpression( t.identifier("styleBlock"), t.identifier("setAttribute") ), [ t.stringLiteral("data-sass-component"), t.identifier("fileName") ] ) ), t.expressionStatement( t.callExpression( t.memberExpression( t.memberExpression( t.identifier("document"), t.identifier("head"), false ), t.identifier("appendChild"), false ), [t.identifier("styleBlock")] ) ) ], [] ), t.blockStatement( [ t.expressionStatement( t.assignmentExpression( "=", t.memberExpression( t.identifier("element"), t.identifier("innerHTML") ), t.identifier("styles") ) ) ], [] ) ) ], [] ), false, false ), [] ) ); } else { const defaultImportName = defaultImportNode.local.name; // Replace import with hardcoded value path.replaceWith( t.variableDeclaration('const', [ t.variableDeclarator( t.identifier(defaultImportName), t.stringLiteral(transpiledContent) ), ]) ); } } } }; } module.exports = transformScss;