Next.JS + AMP CSS - css

I'm having trouble with AMP and CSS in Next.js. In my head component I have:
<Head>
<style amp-custom>{`
// CSS Here
`}</style>
</Head>
In the HTML source it shows up as <style amp-custom=""></style><style>(CSS Here)</style>
In the console I get this error: The mandatory attribute 'amp-custom' is missing in tag 'style amp-custom (transformed)'.
How can I work with AMPHTML's rules on CSS and Next both? Every other method I've tried (such as importing from a file using #zeit/next-sass) causes the CSS to not be rendered at all. This is the only working version I've found.

Try this:
<Head>
<style
amp-custom=""
dangerouslySetInnerHTML={{
__html: `
amp-img {
border: 1px solid black;
}
`,
}}
></style>
</Head>

...It has to be: <style jsx>...</style>. Very dumb mistake that I've been looking for workarounds on all day. :/

As of Sept 2020, I've been having this issue too. I'm new at this, but with no help from the official tutorials. I did find a workaround.
First, I want to point out a couple of things from Next.js that they tell you.
non-AMP page styles are usually placed in _document.js from the next.js example.
</Head>
<style jsx global>{ reset }</style>
<style jsx global>{ globals }</style>
<body>
<Main />
<NextScript />
</body>
They mention in the tutorial to put <style amp-custom>. They don't say where, but it should be within the <Head></Head> of index.js (or whatever .js file for individual pages) OR _document.js for every page.
Ok, sounds good, BUT it's partially wrong!
I will explain what I found it does when turn on amp pages on in Next.JS.
So on an individual page, such as index.js, you need to this code at the top:
export const config = {
amp: true,
}
Then you have to put this in the return function:
const isAmp = useAmp()
Standard instructions from the tutorial. Now AMP is turned on, here's what happens:
Anything in <style amp-custom> is turned into a <style>
anything that is in <style jsx> is turned into a <style amp-custom> tag.
In addition to #2, it injects a unique random index that ruins any css code in that gets put into the generated <style amp-custom> tag.
<style amp-custom>.jsx-2373233908{/* your CSS code that you put in <style jsx> from before */}</style>
and that .jsx-########### throws a "/ error CSS syntax error in tag 'style amp-custom' - incomplete declaration." when you try to compile.
Is this opposite and odd behavior. YES. I don't get why it does it, but I'm a newb.
So my workaround goes like this:
Install your CSS framework package or put your CSS file into the styles folder (let's say located at : ./styles/styles.css)
I also add raw loader from your terminal window. Because I like to put my css in a file, not type it inlined with the code. Let's be realistic, you're going to separate CSS and you'll need to load that file in.
npm install raw-loader --save-dev
Load the CSS in your _document.js (here's my whole _document.js). I use "}" and "{" with fixCSS to escape the .jsx-########### and the injected code magically disappears.
import Document, { Html, Head, Main, NextScript } from 'next/document'
import styleCSS from '!!raw-loader!../styles/styles.css';
const fixCSS = `}${styleCSS}{`;
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html lang="en">
<Head>
</Head>
<style jsx>{`
${fixCSS}
` }</style>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
That's it. Now your imported CSS is shown on AMP pages. Remember this is for sept 2020 using these packages in my package.json:
"dependencies": {
"amp": "^0.3.1",
"next": "^9.5.3-canary.25",
"next-env": "^1.1.1",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"cssnano": "^4.1.10",
"now": "^19.2.0",
"raw-loader": "^4.0.1"
},

Try:
<style jsx amp-custom>
`
... my css
`
</style>
I just tested it out and it worked okay. Not the best approach, NextJS should document the ways to add css in somewhere.

This worked:
const ampStyle = `h1{color:red;}`
<style jsx>{ampStyle}</style>

Related

Vue 3 : disable shadow dom while using custom element

I would like to add in a non-vue application a custom element.
For that, I've created a classical SFC :
//test.ce.vue
<template>
<div class="text-primary">Test</div>
</template>
<script>
export default {
name: 'test',
};
</script>
<style>
.text-primary {
color: red;
}
</style>
And then a main script :
//app.js
import Test from 'test.ce.vue';
const testElement = defineCustomElement(Test);
customElements.define('test-element', testElement);
document.body.appendChild(document.createElement('test-element'));
Everything is running normally with the creation of a shadow dom element :
<test-component>
#shadow-root (open)
<style>
.text-primary {
color: red;
}
</style>
<div class="text-primary">Test</div>
</test-component>
I would like to avoid to redefine .text-primary class in the component as this class is already defined in the main css file. I also don't need to define specific classes for this component only, so in other terms, I would like to remove the shadow dom like a classical custom element will do.
So basically, render this :
<test-component>
<div class="text-primary">Test</div>
</test-component>
Is there's any option to define in vue that permit that ?
Older question, but in case someone still needs a solution for this...
there is currently no way to tell Vue not to use the shadow-dom. In Vue 2 there was a official package for creating web-components without shadow-root. And there is a community port for Vue 3 of that:
https://www.npmjs.com/package/vue3-webcomponent-wrapper
It was only meant to help people who have just migrated from Vue 2 to keep there application working. It was never intended to replace the official solution and should only be used until the official package can handle Vue 3.
Unfortunately that never happend.
The community port still works, but the package does not contain any source code, so it is a bit scary to use.
I came up with another solution for our project. Using defineCustomElement on a more complex vue component wich is composed by a bunch of nested components reveals another problem. The css of the child components wont be copied to shadow root. So only the css of the root component will work.
You can find the related issue and a workaround with full example here:
https://github.com/vuejs/core/issues/4662#issuecomment-1116001438
It basically grabs the css from the head and appends it to the shadow root.
You just have to extend it to also copy your main.css, like
<template>
<div id="app" ref="injectionElementRef">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default defineComponent({
name: 'App',
components: {
HelloWorld
},
mounted() {
const el = this.$refs.injectionElementRef as HTMLElement
const root = el.getRootNode()
const linkTag = document.getElementById('main-css-id')
root.insertBefore(linkTag.cloneNode(), el)
}
});
</script>
The downside of this method is, there is a short flicker because the css is applied after mount. You could show an empty element till css is applied to work around that.
You are using Vue as a Tool to create Web Components, but why use a Tool over Native Technology?
Tools are not better; Tools are only faster in performing a task.
And in your case the Tool does something you do not want it to do.
Using native Web Components Technology, all you need is:
<style>
.text-primary {
color: red;
}
</style>
<test-component></test-component>
<script>
customElements.define("test-component", class extends HTMLElement {
connectedCallback() {
this.innerHTML = `<div class="text-primary">Test</div>`;
}
})
</script>

How to import css file from assets folder in nuxt.js

<template>
<div class="container">
<head>
<link rel="stylesheet" href="~assets/css/style-light.css" />
<link rel="stylesheet" href="~assets/css/login-light.css" />
</head>
</div>
</template>
Importing css like above results in this error
vue.runtime.esm.js:5717 GET http://localhost:3000/~assets/css/login-light.css net::ERR_ABORTED 404 (Not Found)
Is there really no other way loading css other than putting the whole css in the template?
The first thing you need to know is, you can't declare a html head inside any place, neither in yours tamplate, neither in yours components, neither in yours pages, neither in nowhere.
Keep in mind that you can't use a html tags for this, you will use a json schema.
take a look https://nuxtjs.org/guide/configuration for more detailed explanations.
Now about you doubt if you want to import the CSS as globally, the correct place is inside your nuxt.config.js, inside this file, you have a property called head, and inside the head we will configure all the imports.
So, inside nuxt.config.js find your head session, and then create new property called css, some thing like this:
head: {
css: [
'~/assets/style/app.styl',
'~/assets/style/main.css'
],
}
...
Another way, is import your css directly inside your component, for this you can do some thing like this:
<style scoped>
#import '~/assets/style/main.css';
</style>
OR
<style scoped src="#/assets/styles/mystyles.css">
</style>
In Nuxt, you will need a CSS loader instaled in your application too, so have sure you had intalled a "stylus" and "stylus-loader" in your app.
try to impot your css files in script like this :
<script>
import "#/assets/css/style-light.css";
import "#/assets/css/login-light.css";
///
</script>
EDIT: changed ~ to #
You could bring your files in using the head method like so :
head () {
return {
link: [
{ rel: 'stylesheet', href: '/style-light.css' },
{ rel: 'stylesheet', href: '/login-light.css' }
]
}
}
You should also move these css files into the static folder. See this discussion on the Vue forum https://forum.vuejs.org/t/nuxt-import-css-file-and-js-file/42498

How to setup SASS in VueJS project (vue-cli, no webpack config)

My Repo: https://github.com/leongaban/VueJS-RobotBuilder
The course I'm following: https://app.pluralsight.com/player?course=vuejs-fundamentals&author=jim-cooper
This is after I already set it up. Did not remember if there was a sass option.
Anyways currently running into this error:
Invalid CSS after "<": expected 1 selector or at-rule, was ""
I replaced my old <style> CSS stuff here </style>
With:
<style lang="scss">
#import "_robotBuilder.scss";
</style>
I also installed the following packages, but I don't see a webpack config file I can edit.
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
Just fixed my problem.
I removed this:
<style lang="scss">
#import "_robotBuilder.scss";
</style>
Thought it was strange that an import would exist inside a style tag anyways.
Then I cleaned up the .scss file and then just used this import statement inside of the <script> section.
import './_robotBuilder.scss';
Update
Just learned you can scope the <style scoped> tag so styles only apply to the component.
My app.vue (main container) has only this in the style tags
<style lang="scss">
#import url('https://fonts.googleapis.com/css?family=Lato:300,400,400i,700,900&subset=latin-ext');
#import "#/scss/style.scss";
</style>
and works just fine.
And this in the vue.config.js
const path = require("path");
module.exports = {
baseUrl: '/',
outputDir: undefined,
assetsDir: 'assets',
runtimeCompiler: undefined,
productionSourceMap: undefined,
parallel: true,
css: {
modules: false,
loaderOptions: {}
}
}
Turning off the CSS Modularization "might" have something to do with making it work with the imports...

Webpack-React with server-side-rendering: linking to css file in server template with hash name

I'm preparing a starter for react from scratch, here is the code: https://github.com/antondc/react-starter
I managed to set up bundling for client and server, with css modules and less, and now I'm with server side rendering. I'm doing that with a js template:
// src/server/views/index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>INDEX.EJS</title>
<link rel="stylesheet" type="text/css" href="assets/index.css">
</head>
<body>
<div id="app"></div>
<script src="/assets/bundle.js"></script>
</body>
</html>
As you see, the link to the css file is harcoded there. But in my Webpack configuration I have this file name hashed, because I want to prevent caching from browsers when I update the code on development.
I am wondering how can I link the css file there. Now in the template I have href="assets/index.css, but the css file is in /dist/assets/d47e.css.
It would be great if would be possible to do something like href="assets/*.css, but is not possible, so what is the common approach for a problem like this one?
Thanks!
It depends.
Step 1: Get the current asset name
To get the current name of the generated webpack css/js files, you can use the assets-webpack-plugin. This will (with default config) generate an assets.json file in your output folder with essentially this structure:
{
"bundle_name": {
"asset_kind": "/public/path/to/asset"
}
}
Step 2a: Your html is rendered from a template (pug/jade/what ever)
// in your render code
const assets = require('<webpack-output-folder>/assets.json');
// ...
res.render('template', {
scripts: [{src: `${WEBPACK_PUBLIC_PATH}/${assets.myEntryPointName.js}` }],
links: [{href: `${WEBPACK_PUBLIC_PATH}/${assets.myEntryPointName.css}` rel: 'stylesheet' }],
});
// in your template (example for pug)
// ...
each link in links
link(rel=link.rel href=link.href)
// ...
each script in scripts
script(src=script.src)
// ...
Step 2b: Your html is static
You need to update the html (using a script) with the information from the asset.json file. This script needs to be run after webpack. Something like
const assets = require('<webpack-output-folder>/assets.json');
const fs = require('fs');
const css = /assets\/[a-z0-9]*\.css/i;
const js = /assets\/[a-z0-9]*\.js/i;
fs.readFile('<yourhtml>.html', (err, data) => {
// ... (error handling)
const updatedCss = data.replace(css, assets.myEntryPointName.css);
const updatedJs = updatedCss.replace(js, assets.myEntryPointName.js);
fs.writeFile('<yourhtml>.html', updated, (err) => {
// ... (error handling)
});
});
You can use HTMLWebpackPlugin to generate an HTML file that will have your JS and CSS output inserted.

Can't get GulpFile to act the way I want, missing something?

Trying to set up gulp and one of the steps is frustrating. I am following a tutorial and can't get it to work right.
https://css-tricks.com/gulp-for-beginners/
Basically, I want to create a build task that compiles sass, concats the css files, minimizes it, and output it to the public folder. Here is my code for my index.html. (Simplified).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="scss/styles.scss">
<!-- endbuild -->
</head>
<body>
</body>
</html>
Now here is my Gulpfile.js
var gulp = require('gulp'),
sass = require('gulp-sass'),
useref = require('gulp-useref'), // Use to concatenate files
gulpIf = require('gulp-if'),
cssnano = require('gulp-cssnano'),
uglify = require('gulp-uglify'),
imagemin = require('gulp-imagemin'),
imagecache = require('gulp-cache'),
del = require('del'),
runsequence = require('run-sequence');
/* ********************************* */
// PRODUCTION TASKS ONLY \\
/*Used to start with a clean slate on the public folder */
gulp.task('clean:public', function () {
return del.sync('public');
})
gulp.task('watch:prod', function () {
gulp.watch('src/scss/**/*.scss', ['sass']);
});
gulp.task('sass:prod', function () {
return gulp.src('src/scss/**/*.scss')
.pipe(sass())
.pipe(gulp.dest('public/css'))
});
gulp.task('useref:prod', function () {
return gulp.src('src/*.html')
.pipe(useref())
.pipe(gulpIf('*.js', uglify()))
.pipe(gulpIf('*.css', cssnano()))
.pipe(gulp.dest('public'));
});
gulp.task('images:prod', function () {
return gulp.src('src/images/**/*.+(png|jpg|gif|svg)')
.pipe(imagecache(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({plugins: [{removeViewBox: true}]})
])))
.pipe(gulp.dest('public/images'));
});
gulp.task('cssimages:prod', function () {
return gulp.src('src/css/cssimages/**/*.+(png|jpg|gif|svg)')
.pipe(imagecache(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({plugins: [{removeViewBox: true}]})
])))
.pipe(gulp.dest('public/css/cssimages'));
});
/* BRING EVERYTHING TOGETHER */
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public',
['sass:prod','useref:prod','images:prod', 'cssimages:prod'],
callback
)
})
As per the tutorial, this should create a file in the public folder under css names styles.min.css
This file should also already be compiled down from sass. I did an example styles.scss and inside it I have.
$bgcolor : yellow;
body {
background: $bgcolor;
}
div {
width: 100px;
height: 20px;
}
When I run gulp build:prod , this is what it outputs in styles.min.css
$bgcolor:#ff0;body{background:$bgcolor}div{width:100px;height:20px}
The files minimizing fine but i can't get the sass part run right and compile when use the build task.
^^^ As you see, instead of sassing the file and then concatenating the file, it create 2 files. I'm trying to have gulp sass the file first, and then have useref move the file to the public folder and rename it to styles.min.css
It seems I'm missing something somewhere or not sourcing/destinating to the right folders?
If I run gulp sass:prod, it works fine. But can't seem to get my build task to run right I'm stumped.
From the article that you have mentioned,
Gulp-useref concatenates any number of CSS and JavaScript files into a
single file by looking for a comment that starts with "". Its syntax is:
<!-- build:<type> <path> --> ... HTML Markup, list of script / link tags. <!-- endbuild -->
path here refers to the target path of the generated file.
According to the document you have specified the following.
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="scss/styles.scss">
<!-- endbuild -->
So the useref will copy the styles from styles.scss and creates styles.min.css and pastes the scss styles. That is the reason you are getting scss styles in the minified styles.min.css
To achieve what you wanted you have to modify your sass:prod dest path like below.
gulp.task('sass:prod', function () {
return gulp.src('src/scss/**/*.scss')
.pipe(sass())
.pipe(gulp.dest('src/css'))
});
and in the html, you have to reference the css file.
<!--build:css css/styles.min.css -->
<link rel="stylesheet" href="css/styles.css">
<!-- endbuild -->
And also as specified by #Mark, it is better to modify the run-sequence to make sure that the sass:prod task completes before the useref:prod task.
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public','sass:prod',
['useref:prod','images:prod', 'cssimages:prod'],
callback
)
})
From the run-sequence documentatin :
You can still run some of the tasks in parallel, by providing an array of task names for one or more of the arguments.
So, in your tasks array :
['sass:prod','useref:prod','images:prod', 'cssimages:prod'],
these tasks run in parallel. There is no guarantee that the 'sass:prod' task will complete before the 'useref:prod' task. If you want that to happen change to:
gulp.task('build:prod', function (callback){
runsequence
(
'clean:public',
'sass:prod',
['useref:prod','images:prod', 'cssimages:prod'],
callback
)
})

Resources