Vue CLI Run separate CSS build? - css

I'm trying to run a completely separate css/sass build to a specific file.
So I have a folder in my src like:
/src
/sass
./index.sass
./btn.sass
./etc.sass
I'm trying to get it to output to a specific file like "build.css" or whatever which would just end up in the default build directory of "dist" as "dist/build.css".
Been trying to play with vue.config.js and chainWebpack but totally lost here.
Any suggestions how to accomplish this?

One way to do this is to add a Webpack entry that points to the Sass file you want to bundle (using configureWebpack.entry):
// vue.config.js
const { defineConfig } = require('#vue/cli-service')
module.exports = defineConfig({
⋮
configureWebpack: {
entry: {
app: './src/main.js',
extCss: './src/sass/index.sass', 👈
},
},
})
This has a downside as it also generates a .js file that is effectively a no-op. You'd have to delete that manually as a cleanup step:
dist/css/app.css
dist/css/extCss.css # CSS bundle of Sass output
dist/js/app.js
dist/js/chunk-vendors.js
dist/js/extCss.js # no-op file (delete me)
Also delete the <script src="/js/extCss.js"></script> from dist/index.html.

Related

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.

ExtractTextWebpackPlugin always outputs file, even if there are no changes

I have a project that bundles the client-side files using Webpack. We are bundling the CSS using the ExtractTextWebpackPlugin. The problem is when I edit a javascript file the CSS bundle always gets re-built despite there being absolutely no changes to the CSS state.
How can I bundle CSS but only when there are changes to the CSS files?
Extracted from my webpack config:
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader'
})
},
...
plugins: [
new ExtractTextPlugin(isDebug ? '[name].css' : '[name].[chunkhash].css')
],
whenever ExtractTextPlugin sees a import statement with css extension it will automatically extract the text contant of that css file whethere it changes or not
if you are using it to debug then use style-loader and HMR(Hot Module Replacement) for better experence something like this
isDebug
? {
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
: {
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: "css-loader"
})
}
if you want to use current configration and don't want ExtractTextPlugin to build css file and you are importing them in your javascript file using import then somehow you have to remove import statement for css files when there is no change in that css file
and if you are adding css file in your webpack config entry section then it would be easy because webpack allow custom command argument and you can do this by exporting a function in your webpack config file insted of object
//webpack.config.js
module.exports = function(env) {
return {
entry: {
main: env.includeCss
?
["./src/index.js", "./src/main.css"] //build with css
: ["./src/index.js"] //build without css
},
.
.
.
.
}
}
you can pass env.includeCss by command argument like this
webpack --config ./webpack.config.prod.js --env.includeCss
//notice --env.includeCss witch will set env.includeCss as true
run with --env.includeCss normally and without --env.includeCss when you dont want to compile css file
Not directly related to your question but I noticed you use [chunkhash] for extracted CSS. This will make your css name change even if you haven't change content in you CSS files.
Use [contenthash] instead.
https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/#setting-up-hashing
If you are using ExtractTextPlugin, you should use [contenthash]. This way the generated assets get invalidated only if their content changes.

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...

Add grunt task to linemanjs application.js

I want to add a grunt task (specifically angular-template) to my lineman application.js file. There is some documentation found here and here. However, it just tells me to add the grunt task to loadNpmTasks. The problem is that from a fresh project created using lineman, my application.js file does not have a loadNpmTasks array, nor do the comments point out where I should put it. Both examples I have found in the documentations do not show what the application.js file should look like in it's entirety.
The application.js file should look something like this (obviously the src/dest options are not configured correctly:
module.exports = function(lineman) {
return {
loadNpmTasks:['grunt-angular-templates'],
ngtemplates: {
app: {
src: '**.html',
dest: 'templates.js'
}
}
};
};
Then to run the task:
lineman grunt ngtemplates

Resources