How do I import a scss npm module into Ember - css

How do I correctly import a non-plugin npm module into Ember?
I'm trying to use the sass version of flag-icon-css with ember-cli so that the sass is being built during deploy with the rest of ember-cli-sass, but I can't figure out how to do it in an automated fashion (e.g. without manually copying files over to public).
Using ember-auto-import seems like a good place to start but it is more tailored towards javascript imports.
I have tried this configuration in ember-cli-build.js:
'sassOptions': {
includePaths: [
'node_modules/flag-icon-css/sass' // flag-icon.scss
]
},
It will add the styles, but it doesn't include the images used in the styles.
I have read this documentation, but it doesn't specify something more complicated than a single file.

Just use ember-cli-sass:
first add it to includePaths in your ember-cli-build.js
new EmberApp({
sassOptions: {
includePaths: [
'node_modules/flag-icon-css/sass'
]
}
});
use it with #import "flag-icon";
Have a look in the readme.
now while this will sucessfully add the compiled sass output to your /assets/app-name.js there is no automated way to add any kind of assets to your dist folder.
In the case of flag-icon-css it will just add background-image: url(../flags/4x3/gr.svg); to your dist/assets/app-name.css, but not add the svg itself. You can manually do this with broccolis Funnel and MergeTrees:
install broccoli-funnnel and broccoli-merge-trees
import them in ember-cli-build.js:
const Funnel = require('broccoli-funnel');
const MergeTrees = require('broccoli-merge-trees');
use them by replacing return app.toTree() in your ember-cli-build.js with
const flagIcons = Funnel('node_modules/flag-icon-css', { include: ['flags/**/*'] });
return new MergeTrees([app.toTree(), flagIcons]);
so your entire ember-cli-build.js could look like this:
'use strict';
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const Funnel = require('broccoli-funnel');
const MergeTrees = require('broccoli-merge-trees');
module.exports = function(defaults) {
let app = new EmberApp(defaults, {
// Add options here
sassOptions: {
includePaths: [
'node_modules/flag-icon-css/sass'
]
}
});
const flagIcons = Funnel('node_modules/flag-icon-css', { include: ['flags/**/*'] });
return new MergeTrees([app.toTree(), flagIcons]);
};
a short sidenote: I would usually recommend to put assets into the assets folder of your output, but in this case this wont work because the flag-icon-css expects the flags folder to be in the parent directory of the .css.

I figured this out, but I'm not sure it's the best or easiest way. It has some drawbacks.
const EmberApp = require('ember-cli/lib/broccoli/ember-app')
const Funnel = require('broccoli-funnel')
module.exports = function(defaults) {
const app = new EmberApp(defaults, {
'sassOptions': {
includePaths: [
'node_modules/flag-icon-css/sass'
]
}
})
const flags = new Funnel('node_modules/flag-icon-css/', {
srcDir: 'flags',
destDir: '/flags',
})
return app.toTree([flags])
}
The drawback is that the css image urls are not processed, and hardlinked to ../flags, so I have to funnel them into /flags, which is not the convention, as these assets should be compiled into public/assets/images.
This is a two-step implementation (or more steps if the npm module would be more complex). It would be preferred to include just the scss and have (an) Ember (plugin) automatically fetch the dependent resources.

Related

Running gulp postcss in parallel with webpack

I'm working on a project with a pre-configured webpack setup, that I cannot change. I do have access to the package.json though.
I'd like to use nesting in the css files. Every component in src/component has its own folder with a styles.css file. There is auto-reloading for the browser, when I change and save a .css file while the webpack based dev process is running.
I found a workaround hack to get nested css working with postcss by running a gulp watch process in parallel. The gulpfile.js looks like this:
const { src, dest, watch, series, parallel } = require('gulp')
const postcss = require('gulp-postcss')
const rename = require('gulp-rename')
const watchPcss = () => {
watch(['src/**/*.pcss'], { followSymlinks: false }, series(pcss))
}
const pcss = () => {
return src('src/**/*.pcss')
.pipe(postcss([require('autoprefixer'), require('postcss-nested')]))
.pipe(
rename((path) => {
path.extname = '.css'
}),
)
.pipe(dest('./src'))
}
exports.watch = series(watchPcss)
exports.default = series(pcss)
I have additional style.pcss files in each folder, in which I can use nesting. The gulp watch task looks for changes in .pcss files and then runs postcss to convert it to a normal css styles.css file.
This does work. The styles.css files get correctly generated. And saving changes in the .pcss files, do not trigger the webpack watch process.
My plan was that the generated/updated styles.css would trigger webpack and therefore update site in the browser. However, this only works sometimes. Most of the time, all the styles in the .css of the corresponding changed .pcss are not there at all (also not the styles that were in the file before the changes). I then have to save the .css file manually to make the styles show.
Is it possible that the gulp process does not "update" the file, but deletes it and creates a new file? And the webpack watch process notices the "deleting" of the file, but the creating of the new file happens too soon for the watch process to be triggered again?
Is there a way to fix this? Maybe just "touch" the .css file again after xxx ms?
This is probably not the best or "correct" way to make it work, but it seems to do the trick:
const { src, dest, watch, series, parallel } = require('gulp')
const postcss = require('gulp-postcss')
const rename = require('gulp-rename')
const touch = require('gulp-touch-cmd')
const watchPcss = () => {
watch(['src/**/*.pcss'], { followSymlinks: false }, series(pcss, css))
}
const css = () => {
return src('src/**/*.css').pipe(touch())
}
const pcss = () => {
return src('src/**/*.pcss')
.pipe(postcss([require('autoprefixer'), require('postcss-nested')]))
.pipe(
rename((path) => {
path.extname = '.css'
}),
)
.pipe(dest('./src'))
}
exports.watch = parallel(watchPcss)
exports.default = series(pcss)

Tailwind not being applied to library

I have created a library in angular which is styled using tailwind. This is then been push to NPM and then imported into a new project, but the css is not getting applied. I have referenced the node-module path in my tailwind.config.ts:
content: [
"./src/**/*.{html,ts}",
'./node_modules/components-name/**/*.{html,js,ts}'
],
What am i missing?
Tailwind is working if i apply it directly to the new application, it just doesn't work with the imported library.
If you expect all depender apps to utilize tailwind, you can use tailwind classes in your library HTML and have them configure a content path of ./node_modules/my-lib/esm2020/**/*.mjs.
It finds the inlined/escaped classes in the Ivy compiled files.
esm2020 to scope the scan.
Update 11/30/22 - allowing the use of #apply in the library
#applys are not resolved in precompiled library code as these files are not processed in that lifecycle.
As a workaround, you can pre-process your components to resolve #apply styles before building the library.
Create a tailwind.config.js to use in the compilation
If your library project has a demo-app (highly suggest for impl testing), could utilize it's config file, unless you've got some crazy config in there. Since we're not rendering #tailwind components or anything, we won't get any excess styles
projects/my-lib/tailwind.config.js
module.exports = {
content: [
'./projects/my-lib/**/*.{html,ts,css,scss}',
],
};
Note the content path is still relative from project root as that's the context it's ran at
Create precompiler process
Tailwind resolve into a new file (mostly so we don't mess things up accidentally locally)
Point component at the new file
import { readFile, writeFile } from "fs";
import { sync } from 'glob';
import { exec } from 'child_process';
const libRoot = 'projects/my-lib/src/lib';
const tailwindConf = 'tailwind.config.js'; // may be apps/demo when using NX
const processedExt = '.precompiled.scss';
const styleRegex = /styleUrls:\s*\[([^\]]+)]/;
// Find all `.scss` files and tailwind process them
sync(`${libRoot}/**/*.component.scss`).forEach(file => {
const cssFile = file.replace(/\.scss$/, processedExt);
exec(`npx tailwind -c ${tailwindConf} -i ${file} -o ${cssFile}`, (err, stdout, stderr) => {
if (err) {
console.error(stderr);
throw err;
}
});
});
// .component.ts update
// Find all components with `styleUrls` and switch `.scss` extension to our precompiled file names
sync(`${libRoot}/**/*.component.ts`).forEach(file => {
readFile(file, (err, data) => {
if (err) throw err;
const content = data.toString();
const match = content.match(styleRegex);
if (match) {
const styleUrls = match[1]
.split(',')
.map(s => s.trim().replace('.scss', processedExt))
.join(', ');
writeFile(file, content.replace(styleRegex, `styleUrls: [${styleUrls}]`), (err) => {
if (err) throw err;
});
}
});
});
This should only be ran by your CI process and never committed.
Also this could easily be switched to javascript instead of typescript
Other possible ways to do this (untested) without the .component.ts update:
Utilize environment.prod.ts's production: true flag to decide the style file to use
styleUrls: [ environment.prod ? 'my.component.precompiled.scss' : 'my.component.scss' ],
Gotta remember this for all new components
Change the tailwind compile to output to the same scss file
Less moving parts - I liked the separate file so I'd realize quickly if it were accidentally ran/committed
Add CI precompile command to package.json
"build:ci": "node --require ts-node/register projects/my-lib/src/precompile.ts && npm run build:my-lib"
Very rough implementation - remove --require ts-node/register if converted to javascript
I use NX workspace, so I added a new target in the library's project.json:
"ci": {
"executor": "nx:run-commands",
"options": {
"command": "node --require ts-node/register libs/my-lib/src/precompile.ts"
}
},
and added a the package.json entry as:
"build": "nx run-many --all --target build",
"build:ci": "npx nx ci && npm run build",
allowing build to still be used locally.
Build and Package/Release as normal
With #apply's resolved, all should flow well
If you used tailwind utility classes in HTML, be sure to see the very beginning of this answer
Tailwindless Depender
If you want applications to be able to utilize your library without them installing tailwind you could supply a stylesheet containing all the helper classes you used.
Create a stylesheet to contain all used utilities
projects/my-lib/style.scss
#tailwind utilities;
Add a postbuild to your package.json to produce the stylesheet, assuming you use npm run build to build the library.
"postbuild": "npx tailwind -c projects/my-lib/tailwind.config.js -i projects/my-lib/style.scss -o dist/my-lib/style.scss",
Direct depender projects to then include this compiled stylesheet:
#import 'my-lib/style.scss'
Note tailwind does not compile SCSS into CSS - need to run through a SASS processor if you want to supply CSS.
Downside of this is all utility classes used in all components are produced, even if the depender app doesn't use them (same happens for projects using tailwind, so not so bad).
Also the depender project may produce duplicate utility classes if using tailwind itself.
Plus side is your library doesn't require the depender to have tailwind.
Note that you still need the above process to resolve #apply's - this only gathers the utility classes used in the HTML

Webpack: Is there a way to replace imported file path before processing?

Setup: react, webpack, regular css and style loaders, nothing fancy
I want to be able to replace imported file on build step. Like if I build app with env WITH_THEME=true replace one css file with another with different name.
For example, I have 2 css files, style.css and style.theme.css, in react component I import only one file like this import './style.css' (I don't want to change this line, I know it is possible to make if condition with env var here).
So, if I start webpack with WITH_THEME=true I want it to actually import style.theme.css instead of regular style.css, but only if style.theme.css exists.
I came up with this solution, it is test condition for loader:
// ...
test: filePath => {
if (!/\.(s*)css$/.test(path.extname(filePath))) {
return false;
}
const { dir, name, ext } = path.parse(filePath);
const themeFilePath = `${dir}/${name}.theme${ext}`;
if (WITH_THEME && fs.existsSync(themeFilePath)) {
return false;
} else {
return true;
}
},
// ...
But with this way I need to import both style.css and style.theme.css in the component, so webpack could exlude one of them. Is there a better way to do this? Maybe there are some post-css solutions?
What I've actually ended up with:
I made webpack alias for theme-config file, which I import in every .(s)css file (#import '~scss-config';).
resolve: {
// ...
alias: {
'scss-config': path.resolve(
__dirname,
`./src/styles/config-${env.THEME || 'default'}.scss`
)
}
},
It allows to have multiple theme files, but you only need to import single config file, which got replaced with needed theme file in build step.

With Webpack, is it possible to generate CSS only, excluding the output.js?

I'm using Webpack with the extract-text-webpack-plugin.
In my project, I have some build scripts. One of the build scripts is supposed to bundle and minify CSS only. As I'm using Webpack for the other scripts, I found it a good idea to use Webpack even when I only want to bundle and minify CSS.
It's working fine, except that I can't get rid of the output.js file. I don't want the resulting webpack output file. I just want the CSS for this particular script.
Is there a way to get rid of the resulting JS? If not, do you suggest any other tool specific for handling CSS? Thanks.
There is an easy way, no extra tool is required.
There is an easy way and you don't need extra libraries except which you are already using: webpack with the extract-text-webpack-plugin.
In short:
Make the output js and css file have identical name, then the css file will override js file.
A real example (webpack 2.x):
import path from 'path'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
const config = {
target: 'web',
entry: {
'one': './src/one.css',
'two': './src/two.css'
},
output: {
path: path.join(__dirname, './dist/'),
filename: '[name].css' // output js file name is identical to css file name
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].css') // css file will override generated js file
]
}
Unfortunately, that is currently not possible by design. webpack started as a JavaScript bundler which is capable of handling other "web modules", such as CSS and HTML. JavaScript is chosen as base language, because it can host all the other languages simply as strings. The extract-text-webpack-plugin is just extracting these strings as standalone file (thus the name).
You're probably better off with PostCSS which provides various plugins to post-process CSS effectively.
One solution is to execute webpack with the Node API and control the output with the memory-fs option. Just tell it to ignore the resulting js-file. Set the output.path to "/" in webpackConfig.
var compiler = webpack(webpackConfig);
var mfs = new MemoryFS();
compiler.outputFileSystem = mfs;
compiler.run(function(err, stats) {
if(stats.hasErrors()) { throw(stats.toString()); }
mfs.readdirSync("/").forEach(function (f) {
if(f === ("app.js")) { return; } // ignore js-file
fs.writeFileSync(destination + f, mfs.readFileSync("/" + f));
})
});
You can clean up your dist folder for any unwanted assets after the done is triggered. This can be easily achieved with the event-hooks-webpack-plugin
//
plugins: [
new EventHooksPlugin({
'done': () => {
// delete unwanted assets
}
})
]
Good Luck...

Import from parent directory node-sass

I am using node-sass to mock my CDN builds and I am converting my CSS to
a modular Sass design. Basically my setup involves brand sites overwriting
the common styles.
I want to do something like this in the brand folder
#import "../common/global-config";
#import "brand-config";
#import "../common/common";
// brand specific styles here
this file would live at /css/brand-name/brand.scss
the other files would live in /css/common/_common.scss
my node-sass setup looks something like this
function compileSassFile(includes, path, callback) {
if (!fs.existsSync(path)) {
return null;
}
var css = sass.renderSync({
file: path,
success: callback,
outputStyle: 'expanded',
includePaths: includes
});
return css;
}
// bundlePath = 'css', dirName = '/brand-name'
compileSassFile([path.join(bundlePath), path.join(bundlePath, 'common'), path.join(bundlePath, dirName)], path.join.apply(bundlePath, [dirName, 'brand.scss']));

Resources