I am having issues trying to wrap my head around loaders with Encore. Let's take a random plugin for example (https://github.com/Hacker0x01/react-datepicker).
As per the documentation one should first install the plugin using either npm or yarn. Then, you just have to use the import DatePicker from 'react-datepicker'; on top of your component file. Then, it also says that you should add import 'react-datepicker/dist/react-datepicker.css'; and this is where I am having issues.
Before explaining what I've done I must say that the plugin itself is working because if I include the CSS directly (in the HTML using the tag) it works perfectly.
Here is my current webpack.config.js:
// webpack.config.js
var Encore = require('#symfony/webpack-encore');
Encore
.setOutputPath('web/build/')
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.addEntry('app', './assets/js/App.js')
.enableSassLoader()
.autoProvidejQuery()
.enableSourceMaps(!Encore.isProduction())
.configureBabel(function (babelConfig) {
babelConfig.presets.push('es2017');
})
// I have tried to use separate .addLoader using css-loader
// and style-loader, I have also tried to use them together in an array ["style-loader", "css-loader"]
// also tried without the .addLoader directive here, and using:
//
// import 'style-loader!css-loader!react-datepicker/dist/react-datepicker.css';
//
.addLoader({ test: /\.css/, loader: "css-loader"})
.enableReactPreset()
;
var config = Encore.getWebpackConfig();
config.watchOptions = {poll: true, ignored: /node_modules/};
module.exports = config;
The common errors that I am getting everytime I try to run the ./node_modules/.bin/encore dev --watch commands are:
1. If I try to use theimport 'style-loader!css-loader!react-datepicker/dist/react-datepicker.css':
2. If I use the .addLoader({ test: /.css/, loader: "css-loader"}):
(Similar issue happens if I use style-loader!css-loader and/or on an array)
Besides all of this I have to clarify that I have installed the css-loader and the style-loader dependencies.
My question would be, what is the proper way to load css files without having to use the old tag using Symfony2 (3.3 in this case...) and Encore? Or should I leave Encore and use plain webpack/babel from scratch?
Related
New to pnpm, and trying to get my head around some of the basics. But can't find a lot of documentation around it (which often means that it's either very simple, or I'm doing it wrong...).
I have set up a basic pnpm monorepo with an apps and packages folder by basically creating the monorepo folder, running pnpm init and tweaking the result a bit. I got:
package.json
{
"name": "#myorg/root",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
pnpm-workspace.yaml
packages:
- "packages/**"
- "apps/**"
.npmrc
shamefully-hoist=true
Notes:
I did not create an index.js, as I have no idea what to put in there..
I know that I can run/build the various apps from the root folder by adding pnpm run dev with a filter to the scripts section, I've not yet gotten around to setting that up, but I believe it's not critical to running the monorepo. (?)
In the apps folder I've already created some Vue3 apps (this works fine). And now I'd like to move some of the Vue components used there into the packages folder of the monorepo, so I can reuse them in the various apps. This is where i'm getting stuck in the sand...
I'm not entirely sure how much scaffolding you're supposed to add to these shared components. Is each one an entire Vue-project by themselves? (I'm guessing yes), and then, how to specify in that project what parts to export?
I have created the folder "y-theme-select" in the "packages" folder, and ran pnpm init and pnpm add vue on it. Now lets say I want to add the following component (let's keep it very simple):
y-theme-select.vue
<template>
<div>
Hello world!
</div>
</template>
Where do I store it? (eg. packages\y-theme-select\src\y-theme-select.vue?)
How do I export it? (clueless)
How do I import it in a shared project (I recon something like "#myorg/y-theme-select": "1.0.0" in the dependencies section of the package.json?)
How to use Vuetify components in here?
Nb. for completeness sake, found two related questions:
Multiple Vue apps, shared components in a monorepo (unanswered, and doesn't specify pnpm monorepo)
How to create my own component library based on Vuetify
First off, the reason it's not documented at pnpm is that it's, except for a few properties, not a PNPM concern.
Secondly, what I found is that reusable components all share a few basic principles, but other than that can vary fairly wildly in setup.
Thirdly, this answer works. But has a few issues, as described at the end of the answer.
I also want to mention the excellent video by "How To Create A Vue.js Plugin Component" by Erik Hanchett, which laid a foundation to this answer.
UPDATE: I stopped building components. As you add functionality to them there's always some new weird issue. Now that scoped CSS turned out to not work, I've changed direction. Here is a super-simple low-tech solution to creating a library of components in this pnpm monorepo:
import YSwitchLang from "/../../packages/common-vtfy/src/components/YSwitchLang.vue"
Just reference the packages folder from within your apps project. (Fingers crossed I won't run into anything new, but so-far so-good.) The instructions below are still valid, but in this scenario you only need step I.
I. Initialization of the project
I'm creating a package that will hold a few generic and similar Vuetify components, so I will call it "common-vtfy". This project will use Vite+Rollup as bundlers. I also use the rollup-plugin-typescript2 package to create the typescript definitions. You can simply leave out vuetify package if your component doesn't depend on it.
cd packages
pnpm create vue#latest
-> common-vtfy
-> Typescript
-> ESLint
cd common-vtfy
echo auto-install-peers = true > .npmrc
pnpm add -D vuetify rollup-plugin-typescript2
pnpm install
In package.json:
prefix the package name with your monorepo name, like so: "name": "#myorg/common-vtfy".
Move "vue": "^3.2.45" entry to devDependencies (not a biggie to leave it here, because we externalize it as well in the build section of vite.config.ts)
Add peerDependencies (not sure if this is needed, but probably won't hurt):
"peerDependencies": {
"vue": "~3.x",
"vuetify": "~3.x"
}
At this point you could run pnpm dev, and see an otherwise empty Vue project, which has way more stuff than we'll be requiring, so go ahead and delete:
public\favicon.ico
src\assets\logo.*
src\components*
Not sure if we could/should delete the css files from src\assets as well. #TBD.
II. Build component
Now we create the components, and setup App.vue to see the results:
YSwitchTheme.vue
<script setup lang="ts"></script>
<template>
<div>
Hello, I'm YSwitchTheme <v-chip>Vuetify Test</v-chip>
</div>
</template>
And similarly for YSwitchLang.vue
App.vue
<script setup lang="ts">
import YSwitchTheme from "./components/YSwitchTheme.vue" // not required if using the vue plugin system
</script>
<template>
<div>
<YSwitchTheme/>
</div>
</template>
III. Create the plugin
Create two files:
src\components\index.ts
export {default as YSwitchLang} from "./YSwitchLang.vue"
export {default as YSwitchTheme} from "./YSwitchTheme.vue"
I believe that this "registers" the components, but the details are not exactly clear to me.
src\CommonVtfyPlugin.ts
The plugin entry file. More information: https://vuejs.org/guide/reusability/plugins.html#writing-a-plugin
I have tried to export the components both as a plugin, and as a individually importable components, which does not require the user to load it as a plugin. However, this did not end up working, so I've commented out that last bit. The plugin must be imported using the Vue plugin system (more on that later)
import type { App } from "vue"
import { YSwitchLang, YSwitchTheme } from "./components"
// Export as plugin
export default {
install: (app: App) => {
app.component("YSwitchLang", YSwitchLang)
app.component("YSwitchTheme", YSwitchTheme)
}
}
// Export as individually importable components
// export { YSwitchLang, YSwitchTheme }
IV. "Local" testing/usage demonstration
To use the plugin we add it to our main.ts, and this is something we can do in this same project. The resulting code is the same as you would use when you are importing it later in your other projects.
main.ts
Add import:
import CommonVtfyPlugin from './CommonVtfyPlugin'
If you're using Vuetify, then also add:
// Vuetify
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const vuetify = createVuetify({
components,
directives,
})
And add the .use clause, in the following manner:
const app = createApp(App)
app.use(vuetify).use(CommonVtfyPlugin)
app.mount('#app')
Now in App.vue, comment out the import statements.
V. Build it
Here we're going to use rollup-plugin-typescript2 to generate the typescript files.
vite.config.ts
Add to the imports:
import vuetify from "vite-plugin-vuetify"
import typeScript2 from "rollup-plugin-typescript2"
Add to the plugins:
vuetify({
autoImport: true,
}),
typeScript2({
check: false,
include: ["src/components/*.vue"],
tsconfigOverride: {
compilerOptions: {
sourceMap: true,
declaration: true,
declarationMap: true,
}
},
exclude: [
"vite.config.ts"
]
})
Add a new section build to the defineConfig:
build: {
cssCodeSplit: false,
lib: {
entry: "./src/CommonVtfyPlugin.ts",
formats: ["es", "cjs"],
name: "CommonVtfyPlugin",
fileName: format => (format == "es" ? "index.js" : "index.cjs"),
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue"
}
}
}
},
Now you're ready to build it by running pnpm build.
Wrap it up by updating package.json, adding four properties:
"type": "module",
"exports": {
".": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
One issue the I've not yet figured out is how to generate a single index.d.ts declarations file. For now I just create the following file by hand in the dist folder. Inspired by the Vuetify project, I have not yet figured out why/how this works.
index.d.ts
declare module '#myorg/common-vtfy' {
import { VueConstructor } from 'vue'
const YSWitchLang: VueConstructor
const YSWitchTheme: VueConstructor
export {
YSWitchLang,
YSWitchTheme
}
}
VI. Use it!
Go back to a project that wants to use these components and add them to the project using pnpm add #myorg/common-vtfy (replace myorg with the name of your monorepo). You should see a new dependency in the package.json file that reads something like "#myorg/common-vtfy": "workspace:^1.0.0".
main.ts or plugins\index.ts (wherever you load your plugins)
Import the components:
import YSwitchTheme from '#myorg/common-vtfy'
import YSwitchLang from '#myorg/common-vtfy'
I was expecting to be able to import the modules using a {}-style import, but this doesn't work. I think this means that we're not correctly exporting the components from the plugin. See issues section.
import { YSwitchTheme, YSwitchLang} from '#myorg/common-vtfy'
And finally, to use the plugin, do:
app.use(YSwitchTheme)
app.use(YSwitchLang)
VII. Updating
At some point you're going to make changes to the component.
Component: Make the changes
Component: pnpm build
Component: Recreate index.d.ts
Application: pnpm update #myorg/common-vtfy
Open issues
Scoped CSS is ignored. I've tried all kinds of different rollup settings.
Confirm that peerDepencies is either a good thing or useless.
I've not been able to figure out how to generate a single index.d.ts typescript declaration file.
Pnpm monorepo update
I think you should run the pnpm update on the whole repo. But haven't dived into that yet.
I will attempt to update and further refine this answer as I gain understanding, and/or when usefull comments are posted here.
I'm currently upgrading an existing app with legacy per-page frontend javascript and CSS to Symfony's Webpack Encore. The app and it's dependencies each supply their own page-specific JavaScript or CSS in a webpack.config.js. For example:
MyApp
// webpack.config.js
Encore
// ...
.addEntry('app', './assets/app.js')
.addEntry('checkout', './assets/checkout.js')
.addEntry('account', './assets/account.js')
// ...
TheDependencies
// webpack.config.js
Encore
// ...
.addEntry('login', './assets/login')
.addEntry('logout', './assets/logout')
// ...
Naturally the easy solution is to manually copy the dependencies entries into MyApp :
// webpack.config.js
Encore
// ...
.addEntry('app', './assets/app.js')
.addEntry('checkout', './assets/checkout.js')
.addEntry('account', './assets/account.js')
.addEntry('login', './node_modules/my-dependency/assets/login')
.addEntry('logout', './node_modules/my-dependency/assets/logout')
// ...
However, this pose problems (1) in the event the dependency is updated with new entries or (2) when multiple dependencies (~5) each supply multiple (10+) entries.
I want to find a way to easily import every page-specific entries from each dependencies. For example, something similar to this pseudo-code:
// webpack.config.js
Encore
// ...
.addEntry('app', './assets/app.js')
.addEntry('checkout', './assets/checkout.js')
.addEntry('account', './assets/account.js')
.addEntries('./node_modules/my-dependency/webpack.config.js')
// ...
Is that possible and how could I achieve this ?
I propose an answer that is not directly related to webpack. Even though you're maybe not using a JS framework, since 2015 (ES6), JavaScript has had the ES6 modules standard to import modules in Node.js, also supported by most browsers.
for example for your account (I assume it contains your login and logout in this example) and this is just a
Using package.json:
{
"type": "module" }
With this, your source code should use import syntax
Then login.js (all your script here I put a function example) :
export function login() {
return "you're logged in";
}
Then account.js
import { login } from './login.js';
let val = login(); val is "logged";
You can then just import your script account.js like you did in your webpack.
In the end, you can add few entries in your webpack config, depending on which scripts are loading in each script with the import call, but in a way it allows to organize them.
I'm in the process of upgrading a website from Webpack1 to Weback3, and I'm having trouble translating the scss loader into the Webpack 3 format because of the following line:
css?-autoprefixer&sourceMap!postcss?parser=postcss-scss!sass?sourceMap'
I've figured out that to translate this line to Webpack3, I need to add the "-loader" suffix to sone of the items, for exmaple "css" becomes "css-loader."
I can even get the site to compile if I take out the last-half of the line, and include only "css-loader," but the homepage styling is broken. For some reason, if I include the whole source map section, it fails to compile with error "ERROR in Entry module not found: Error: Can't resolve 'css?-autoprefixer&sourceMap'"
The original loader in Webpack 1 was:
loaders: [
{
loader: ExtractTextPlugin.extract('style-loader', 'css?-autoprefixer&sourceMap!postcss?parser=postcss-scss!sass?sourceMap'),
test: /\.scss$/
},
In Webpack 3, I have translated it to:
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [require.resolve('css-loader?-autoprefixer&sourceMap!postcss-loader?parser=postcss-scss!sass-loader?sourceMap'), require.resolve('sass-loader')]
})
},
If anyone can give me some pointers on how to get around this module not found error, I would be very grateful. I've spent hours reviewing the documentation, and googling, and I can't seem to find a clear guide as to what all of the connector symbols in the line mean (! = ? &) or why it now returns module not found.
The symbols have to do with inline loaders. From the documentation:
It's possible to specify loaders in an import statement, or any equivalent "importing" method. Separate loaders from the resource with !. Each part is resolved relative to the current directory.
import Styles from 'style-loader!css-loader?modules!./styles.css';
It's possible to overwrite any loaders in the configuration by
prefixing the entire rule with !.
Options can be passed with a query parameter, e.g. ?key=value&foo=bar,
or a JSON object, e.g. ?{"key":"value","foo":"bar"}.
https://webpack-3.cdn.bcebos.com/concepts/loaders/#inline
I have a web application built with webpack. I have many styling variations, and those styles are all called style.css and in their own respective directories like
./STYLE_A/style.scss
./STYLE_B/style.scss
./STYLE_F/style.scss
I am supplying a cross-env variable STYLE_DIR to webpack and I want that variable to control where the scss gets included from.
I've tried:
require(`./${STYLE_DIR}/style.scss`); //in the webpack (does nothing)
I've tried:
require(`./${STYLE_DIR}/style.scss`); //in my client.js (ends up including every style.scss from every one of the style directories)
I've tried setting this to a 'process.env' variable in webpack, I've tried using an alias to resolve, there's something I'm just missing.
I got interested in your question, then I did a little research and I think I have a way to make it work.
Steps:
1.
In Webpack config file use DefinePlugin in order to have a constant that can be setup at compile time. You do that in this way:
const GLOBALS = {
'process.env.STYLE_DIR': JSON.stringify(process.env.STYLE_DIR)
};
export default {
entry: [
'./app/index'
],
output: {
path: path.resolve(__dirname, '/dist'),
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin(GLOBALS)
],
...
}
2.
Put your style.scss file in the correct folders (STYLE_A, STYLE_B and STYLE_C as you indicated).
3.
In your .js file require your SCSS file as follow (of course be sure to have the corresponding loaders properly setup in Webpack config file):
require(`./${process.env.STYLE_DIR}/style.scss`);
4.
Set the STYLE_DIR variable before you run webpack. Something like this:
export STYLE_DIR = 'STYLE_A'
This is working for me. If I change the STYLE_DIR value before running Webpack I get a different style file imported.
I hope this helps.
I'm using WebPack and now I'm going to need to do the compilation of SASS fiels (we'll be working with Bootstrap). First of all, I installed node-sass and sass-loader (not sure if any of those is needed at all). Then, in my server.js I've added the following line (nothing more that would refer to the Sass object, yet).
import Sass from "node-sass";
The compilation gave me errors that fs package is missing so based on this answer I've tried the following (both individually and simultaneously). The error about fs disappeared but instead there's something about unexpected token in the package.json of the node-sass module.
node: { fs: "empty" },
target: "node",
...
I have no idea what those would do and how those affect anything. Both seem to be produce the same error. Any light on that would be an extra bonus as they appear quite different from each other.
Then, I also tried to enable to the loader in my webpack.config.js by the following addition suggested here. Even tried to map the source like described here. Another post suggests using a different syntax for target specification... Nothing seems to help and finally I got totally confused and gave up.
module: {
loaders: [
{ test: /\.js$/, loader: "babel", exclude: /node_modules/ },
{ test: /\.scss$/, loader: 'style!css!sass' }
]
},
sassLoader: { sourceMap: true },
...
Some of the posts I'm reading are old and I feel that they might be outdated and do more harm than good. And there seems to be quite a few posts not answered that regard the connection between WebPack and SASS (e.g. this and this). I fear that I might be barking up the wrong tree...
I need help being pointed in the right direction.
There should be no need to write import Sass from "node-sass"; anywhere in your code. The node-sass package is part of your build-pipeline which is using webpack and thus running in a node environment. But your front-end code typically does not have any connection to a node env because it's running in the browser. This also explains why you got errors related to fs. You should also get rid of your node: { fs: "empty" }, target: "node", ... stuff. In a pure front-end related webpack pipeline you shouldn't need those hacks.
Try the following in your webpack.config.js file:
module.exports = {
...
module: {
loaders: [
{
test: /\.scss$/,
loaders: ["style-loader", "css-loader", "sass-loader"]
}
]
}
};
To get things going don't use source-maps from the beginning, but add them once the rest of the setup is running. To get sourcemaps each loader must re-use the result from the previous loader, which is why you should start with having a look how sourcemaps are configured for sass-loader.