-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Build CSS assets with Webpack #26820
Conversation
webpack.config.js
Outdated
@@ -191,6 +194,10 @@ function getWebpackConfig( { externalizeWordPressPackages = false } = {}, argv ) | |||
loader: 'sass-loader', | |||
options: { | |||
includePaths: [ path.join( __dirname, 'client' ) ], | |||
data: ` | |||
@import '${ path.join( __dirname, 'assets/stylesheets/shared/_colors.scss' ) }'; | |||
@import '${ path.join( __dirname, 'assets/stylesheets/shared/_utils.scss' ) }'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's enough to import just _utils.scss
here because it in turn imports colors:
@import '../shared/colors'; // import all of our wpcom colors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out. Addressed in 3bbffee
webpack.config.js
Outdated
@@ -113,6 +113,9 @@ const wordpressExternals = ( context, request, callback ) => | |||
*/ | |||
// eslint-disable-next-line no-unused-vars | |||
function getWebpackConfig( { externalizeWordPressPackages = false } = {}, argv ) { | |||
const cssFilename = | |||
isDevelopment || calypsoEnv === 'desktop' ? 'style-[name].css' : 'style-[name].[chunkhash].css'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we necessarily need style-
prefix? The .css
extension already tells us it's a stylesheet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really need the prefix, it's just that our .gitignore
file doesn't ignore all /public/*.css
files, but only ones that match certain patterns: /public/style*.css
. Easy to change, of course, but I choose the path of least resistance :)
webpack.config.js
Outdated
@@ -113,6 +113,9 @@ const wordpressExternals = ( context, request, callback ) => | |||
*/ | |||
// eslint-disable-next-line no-unused-vars | |||
function getWebpackConfig( { externalizeWordPressPackages = false } = {}, argv ) { | |||
const cssFilename = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this configurable? That would help SDK because if I now run:
npm run sdk:gutenberg -- --editor-script=client/gutenberg/extensions/todo/block.js
That would compile a file client/gutenberg/extensions/todo/build/style-todo-editor.css
(with style-
) instead of client/gutenberg/extensions/todo/build/todo-editor.css
Perhaps something like this:
function getWebpackConfig( { cssFilename, externalizeWordPressPackages = false } = {}, argv ) {
if ( ! cssFilename ) {
cssFilename = isDevelopment || calypsoEnv === 'desktop' ? 'style-[name].css' : 'style-[name].[chunkhash].css';
}
And in SDK:
const baseConfig = getBaseConfig( { cssFilename: '[name].css', externalizeWordPressPackages: true } );
Jest is failing for this because it doesn't know what to do with scss imports. I solved this in another PR (#26683) by mocking style imports, so you might want to look into that solution: https://github.com/Automattic/wp-calypso/blob/c72ff9c44422589813bdb43f270e619827419aef/test/test/helpers/styles/README.md |
Do you have any grasp how many of those we have? Is this something we can fix and discourage Calypso-wide? |
If this becomes a blocker, can we scope this PR down to not contain any style imports in some of the JS files for now? I.e. just keep the build and global |
I have no idea. I discovered the It can and should be fixed by making the selectors more specific: instead of declaring
Like committing the Webpack CSS build pipeline, but not actually using it for anything? Well that's possible, but suboptimal :) |
4b9abfd
to
4cf0cce
Compare
I pushed an update to this PR that adds RTL support. There are three steps:
Step 3 works only when user bootstrapping is in action. If the server is not capable to retrieve the authenticated user's locale info, it will always send the LTR version. There is code in the client (search for |
webpack.config.js
Outdated
@@ -12,7 +12,8 @@ const fs = require( 'fs' ); | |||
const path = require( 'path' ); | |||
const webpack = require( 'webpack' ); | |||
const AssetsWriter = require( './server/bundler/assets-writer' ); | |||
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); | |||
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin-with-rtl' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename the import to reflect the WithRtl
part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'd rather keep the name without a suffix. It's a forked plugin that will likely be merged into upstream sooner or later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to actively lobby the upstream project for this to happen tho...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to actively lobby the upstream project for this to happen tho...
I just got involved by commenting on an issue and by filing another, somewhat related one.
This seems to be working pretty well! In order to divide & conquer, would it make sense to move the webpack config changes (that essentially enable RTL) to a separate PR (which will only affect bundles built by the SDK for now), and to sort out the remaining questions for Calypso as a whole separately? |
webpack.config.js
Outdated
@@ -113,6 +114,9 @@ const wordpressExternals = ( context, request, callback ) => | |||
*/ | |||
// eslint-disable-next-line no-unused-vars | |||
function getWebpackConfig( { externalizeWordPressPackages = false } = {}, argv ) { | |||
const cssFilename = | |||
isDevelopment || calypsoEnv === 'desktop' ? 'style-[name].css' : 'style-[name].[chunkhash].css'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this when we build a Gutenberg extension from the SDK for production, the output CSS files will contain the chunkhash in the name:
NODE_ENV=production npm run sdk:gutenberg -- --editor-script=client/gutenberg/extensions/editor-notes/index.js
Perhaps it's a good idea to keep it only style-[name].css
for the SDK, regardless of whether we're building for development or production.
Changes the ignore rules to ignore all CSS and CSS sourcemap files in `/public`. There are no version-controlled CSS files in that directory and the current rules are overly specific. They were created a long time ago in pre-OSS era. Removes unnecessary constraints on naming of CSS chunks in #26820.
Hear hear, this PR is ready for review and introduces the first bit of production CSS built with Webpack into Calypso. What are the changes in this PR? Webpack "assets" for the app entrypoint or a chunk used to be just JS files, but now there are three types of them: JS, CSS.LTR and CSS.RTL. When creating the I created a fork of the When switching locale at runtime, there's new code ( Finally I converted the |
function switchWebpackCSS( isRTL ) { | ||
const currentLinks = document.querySelectorAll( 'link[rel="stylesheet"][data-webpack]' ); | ||
|
||
return map( currentLinks, async currentLink => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to return an array of promises? Doesn't appear it's used...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's right, nobody waits for the result. That's true for the whole switchLocale
function that does a lot of async updates -- classic CSS, webpack CSS, loading the translation file... We just call switchLocale
and never look back.
I think it's still "good manners" to return such a promise, so I'm reluctant to remove the code. 🎵 We shall surely use it one day 🎵
package.json
Outdated
@@ -132,7 +132,7 @@ | |||
"lru": "3.1.0", | |||
"lunr": "2.3.3", | |||
"marked": "0.5.0", | |||
"mini-css-extract-plugin-with-rtl": "0.1.3", | |||
"mini-css-extract-plugin-with-rtl": "github:jsnajdr/mini-css-extract-plugin-with-rtl", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we either publish this fork to npm under a new name or at least move the github repo under Automattic? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to the A8c org in e16fb8e
mini-css-extract-plugin-with-rtl-and-webpack-attr
is too long a name :)
Server build doesn't need to process any CSS imports -- that's only for Calypso client build. Fixed build errors in Automattic/wp-calypso#26820.
The desktop build should become green again after Automattic/wp-desktop#503 is merged. |
Server build doesn't need to process any CSS imports -- that's only for Calypso client build. Fixed build errors in Automattic/wp-calypso#26820.
e16fb8e
to
46e8fc4
Compare
currentLink.parentElement.removeChild( currentLink ); | ||
} | ||
} ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some overlap in terms of functionality of this function with what we're doing in DocumentHead
to change the document title on the client side when switching between different sections.
I wonder if we could extend DocumentHead
to also cover the CSS link case, and then dispatch setDocumentHeadLink
when flipping betwwen LTR and RTL. (A word of caution, DocumentHead
, and when to change or reset what, has been a source of subtle bugs, due to the many requirements there are for it -- amoung them SSR). However, it might be worth centralizing this kind of logic. (I don't want to block this PR on it though, can't wait to see this one at work 🙂 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'll pass on this idea 🙂
DocumentHead
owns the document title and some of the meta
and link
elements (but not all). The source of truth is Redux state, both in server and client rendering, and DocumentHead
is a client-side tool that makes sure that the DOM is updated.
The script and stylesheet links, on the other hand, are not owned by Redux or any app state. They are owned by webpack. setLocaleInDOM
updates these webpack-owned links in a careful cooperation with the webpack runtime.
I think these two very distinct concepts should be mixed together. The only thing they have in common is that the stylesheets live in the head
element, that's all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, fair enough.
return map( currentLinks, async currentLink => { | ||
const currentHref = currentLink.getAttribute( 'href' ); | ||
const newHref = flipRTL( currentHref, isRTL ); | ||
if ( currentHref === newHref ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this ever happen, given what flipRTL
does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic is "put the URL into the desired state (LTR or RTL)" rather than "toggle the state from the current state to the other one". Maybe flipRTL
has a misleading name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, might be worth renaming 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to setRTLFlagOnCSSLink
and added doc comment to the function.
I tested successfully:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests well and looks good 👍 (Good job on getting blockers out of the way in separate PRs!)
I have one question, I noticed that while our CLI produced stylesheets are loaded with a hash query arg (e.g. /calypso/style.css?v=d247105eb8
), that's not the case for the webpack-produced ones in this PR. Shouldn't they also get one?
The stylesheets get a hash in production mode, .e.g.,
Just like the JS chunks. In development mode, there is no hash. Neither for JS nor CSS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
46e8fc4
to
f382dfa
Compare
…m into index.html Until now, Webpack generated only JS assets, but now there are also CSS. When generating index.html on the server, each type needs a different tag: `<link rel="stylesheet">` vs `<script>`. Divide the assets into groups and insert appropriate tags.
6a134c5
to
4ea0b45
Compare
Latest status:
This PR evolved from something that was a proof of concept into a finished production-ready PR. See #26820 (comment) for more info about the final state of things.
Original description from the proof-of-concept era:
This is a proof of concept of migrating the CSS build pipeline to Webpack. Moves a few components' styles to
import './style.scss'
in the JS module, and makes sure that the CSS is built with Webpack and the stylesheets are included in the server-generated index.html on initial load.Code splitting also seems to work very well: Webpack automatically creates CSS chunks for async-loaded sections and loads the stylesheets together with scripts when loading the section. Our custom code for "sections CSS" can be eventually removed.
See individual commit messages for details.
Largest missing piece is support for RTL. Completely ignored at the moment. I'm afraid we'll need to write a custom Webpack plugin, a nontrivial one, to make it work as we need it:
If we wanted to do a gradual migration and keep both CSS pipelines in place for some time, there is one gotcha I discovered that makes this very fragile: we have some CSS rules where specificity is determined by rule order and nothing else. Example:
What is the resulting
font-weight
of the button? It depends on the order of the rules. The last one wins. It's very easy to break this when moving stylesheets around, as the order can change.Another todo: patch the other index files in
client/document
(desktop, browsehappy, support-user) to also include links to the right stylesheets.How to test:
Run Calypso both in debug (
npm run start
) and production (CALYPSO_ENV=production npm run build
) modes and verify that all stylesheets are loaded as expected.