Skip to content
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

Improve heft-webpack-basic-tutorial to illustrate .scss support #3

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
8,119 changes: 2,639 additions & 5,480 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion common/config/rush/repo-state.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{
"pnpmShrinkwrapHash": "bc4e7aed1c0d15c3e770eb41cddc9b0c6dce54f6"
"pnpmShrinkwrapHash": "67ffd7e6d1eea39ab8245465c4563f09f775a35c"
}
4 changes: 2 additions & 2 deletions heft/heft-node-basic-tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
"build": "heft test --clean"
},
"devDependencies": {
"@rushstack/eslint-config": "^2.3.4",
"@rushstack/eslint-config": "~2.3.4",
"@rushstack/heft": "^0.33.0",
"@rushstack/heft-jest-plugin": "^0.1.3",
"@types/heft-jest": "1.0.2",
"@types/node": "10.17.13",
"eslint": "~7.12.1",
"eslint": "~7.28.0",
"typescript": "~3.9.7"
}
}
4 changes: 2 additions & 2 deletions heft/heft-node-jest-tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"build": "heft test --clean"
},
"devDependencies": {
"@rushstack/eslint-config": "^2.3.4",
"@rushstack/eslint-config": "~2.3.4",
"@rushstack/heft": "^0.33.0",
"@rushstack/heft-jest-plugin": "^0.1.3",
"@types/heft-jest": "1.0.2",
"@types/node": "10.17.13",
"eslint": "~7.12.1",
"eslint": "~7.28.0",
"typescript": "~3.9.7"
}
}
2 changes: 1 addition & 1 deletion heft/heft-node-rig-tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "heft test --clean"
},
"devDependencies": {
"@rushstack/eslint-config": "^2.3.4",
"@rushstack/eslint-config": "~2.3.4",
"@rushstack/heft": "^0.33.0",
"@rushstack/heft-node-rig": "^1.0.31",
"@types/heft-jest": "1.0.2",
Expand Down
2 changes: 1 addition & 1 deletion heft/heft-webpack-basic-tutorial/config/heft.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
/**
* The path to the plugin package.
*/
"plugin": "@rushstack/heft-webpack4-plugin"
"plugin": "@rushstack/heft-webpack5-plugin"

/**
* An optional object that provides additional settings that may be defined by the plugin.
Expand Down
2 changes: 1 addition & 1 deletion heft/heft-webpack-basic-tutorial/config/typescript.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
/**
* File extensions that should be copied from the src folder to the destination folder(s).
*/
"fileExtensions": [".css"]
"fileExtensions": [".css", ".scss", ".sass"]

/**
* Glob patterns that should be explicitly included.
Expand Down
34 changes: 20 additions & 14 deletions heft/heft-webpack-basic-tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@
"start": "heft start"
},
"devDependencies": {
"@rushstack/eslint-config": "^2.3.4",
"@rushstack/heft": "^0.33.0",
"@rushstack/eslint-config": "~2.3.4",
"@rushstack/heft-jest-plugin": "^0.1.3",
"@rushstack/heft-webpack4-plugin": "^0.1.27",
"@rushstack/heft-webpack5-plugin": "~0.1.27",
"@rushstack/heft": "^0.33.0",
"@types/heft-jest": "1.0.2",
"@types/react": "16.9.45",
"@types/react-dom": "16.9.8",
"@types/webpack-env": "1.13.0",
"css-loader": "~4.2.1",
"eslint": "~7.12.1",
"html-webpack-plugin": "~4.5.0",
"react": "~16.13.1",
"react-dom": "~16.13.1",
"style-loader": "~1.2.1",
"@types/react-dom": "17.0.7",
"@types/react": "17.0.11",
"@types/webpack-env": "1.16.0",
"autoprefixer": "~10.2.6",
"css-loader": "~5.2.6",
"eslint": "~7.28.0",
"file-loader": "~6.2.0",
"html-webpack-plugin": "~5.3.1",
"postcss-loader": "~6.1.0",
"postcss": "~8.3.4",
"react-dom": "~17.0.2",
"react": "~17.0.2",
"sass-loader": "~12.1.0",
"sass": "~1.35.1",
"source-map-loader": "~3.0.0",
"style-loader": "~2.0.0",
"typescript": "~3.9.7",
"webpack": "~4.44.2",
"source-map-loader": "~1.1.2"
"webpack": "~5.39.0"
}
}
2 changes: 2 additions & 0 deletions heft/heft-webpack-basic-tutorial/src/ExampleApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ToggleSwitch, IToggleEventArgs } from './ToggleSwitch';
*/
export class ExampleApp extends React.Component {
public render(): React.ReactNode {
// This is an example of an inline style object.
// See ToggleSwitch.tsx for an example of a style that is imported from a SASS .scss file.
const appStyle: React.CSSProperties = {
backgroundColor: '#ffffff',
padding: '20px',
Expand Down
31 changes: 31 additions & 0 deletions heft/heft-webpack-basic-tutorial/src/ToggleSwitch.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Documentation for .scss syntax: https://sass-lang.com/documentation/syntax

// Example of a SASS variable:
$height: 20px;

.frame {
border-radius: 10px;
width: 35px;
height: $height;
cursor: pointer;
}

.sliderLeft {
border-radius: 10px;
background-color: #c0c0c0;
width: 20px;
height: $height;

margin-left: 0px;
margin-right: auto;
}

.sliderRight {
border-radius: 10px;
background-color: #c0c0c0;
width: 20px;
height: $height;

margin-left: auto;
margin-right: 0px;
}
32 changes: 9 additions & 23 deletions heft/heft-webpack-basic-tutorial/src/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';

import styles from './ToggleSwitch.scss';

/**
* Slider positions for `ToggleSwitch`.
*/
Expand Down Expand Up @@ -54,33 +56,17 @@ export class ToggleSwitch extends React.Component<IToggleSwitchProps, IToggleSwi

public render(): React.ReactNode {
const frameStyle: React.CSSProperties = {
borderRadius: '10px',
backgroundColor:
this.state.sliderPosition === ToggleSwitchPosition.Left
? this.props.leftColor
: this.props.rightColor,
width: '35px',
height: '20px',
cursor: 'pointer'
};
const sliderStyle: React.CSSProperties = {
borderRadius: '10px',
backgroundColor: '#c0c0c0',
width: '20px',
height: '20px'
this.state.sliderPosition === ToggleSwitchPosition.Left ? this.props.leftColor : this.props.rightColor
};

if (this.state.sliderPosition === ToggleSwitchPosition.Left) {
sliderStyle.marginLeft = '0px';
sliderStyle.marginRight = 'auto';
} else {
sliderStyle.marginLeft = 'auto';
sliderStyle.marginRight = '0px';
}

return (
<div style={frameStyle} onClick={this._onClickSlider}>
<div style={sliderStyle} />
<div className={styles.frame} style={frameStyle} onClick={this._onClickSlider}>
<div
className={
this.state.sliderPosition === ToggleSwitchPosition.Left ? styles.sliderLeft : styles.sliderRight
}
/>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion heft/heft-webpack-basic-tutorial/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"rootDirs": ["src/", "temp/sass-ts/"],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @iclanton will remember for certain, but I vaguely remember there being something counterintuitive about the "rootDirs" field that required us to keep both "rootDir" and "rootDirs". I think "rootDir" is used for calculating the root path and if you don't have a TS file in your "src" folder, then you might lose your folder structure without it being also specified.

Copy link
Collaborator Author

@octogonz octogonz Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halfnibble good catch!

You are right, despite the name, rootDir and rootDirs are very different settings:

  • rootDir explicitly specifies the folder that will be used as the basis for lib output; otherwise it will be inferred from the common parent of the files, include, etc
  • rootDirs specifies directories with additional typings that will be overlayed on the main src directory tree, under the assumption that those .js files will all get copied into the same output folder.

Specifying rootDirs does not influence the rootDir.

As an experiment, with "rootDir": "src" deleted in this branch, if I move all my files to be src/x/*.ts they wrongly get compiled to lib/*.ts instead of lib/x/*.ts.

The naming of these settings is very misleading.


"forceConsistentCasingInFileNames": true,
"jsx": "react",
Expand Down
137 changes: 133 additions & 4 deletions heft/heft-webpack-basic-tutorial/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use strict';

const path = require('path');
const sass = require('sass');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { DefinePlugin } = require('webpack');

/**
* If the "--production" command-line parameter is specified when invoking Heft, then the
Expand All @@ -12,45 +15,171 @@ function createWebpackConfig({ production }) {
// Documentation: https://webpack.js.org/configuration/mode/
mode: production ? 'production' : 'development',
resolve: {
// Important: Do NOT add TypeScript extensions here
extensions: ['.js', '.jsx', '.json']
},
module: {
rules: [
{
test: /\.css$/,
use: [require.resolve('style-loader'), require.resolve('css-loader')]
// We recommend the newer .scss file format because its syntax is a proper superset of plain CSS.
// The older .sass syntax is supported only for backwards compatibility.
// The SASS docs are here: https://sass-lang.com/documentation/syntax
test: /\.(scss|sass|css)$/,
exclude: /node_modules/,
use: [
{
// Generates JavaScript code that injects CSS styles into the DOM at runtime.
// The default configuration creates <style> elements from JS strings
// https://www.npmjs.com/package/style-loader
loader: 'style-loader'
},

{
// Translates CSS into CommonJS
// https://www.npmjs.com/package/css-loader
loader: 'css-loader',
options: {
// 0 => no loaders (default);
// 1 => postcss-loader;
// 2 => postcss-loader, sass-loader
importLoaders: 2,

// Enable CSS modules: https://github.com/css-modules/css-modules
modules: {
// The "auto" setting has a confusing design:
// - "false" disables CSS modules, i.e. ":local" and ":global" selectors can't be used at all
// - "true" means magically disable CSS modules if the file extension isn't like ".module.css"
// or ".module.scss"
// - a lambda disables CSS modules only if the lambda returns false; the function parameter is
// the resource path
// - a RegExp disables CSS modules only if the resource path does not match the RegExp
//
// NOTE: Counterintuitively, if you instead set "modules=true" then CSS modules are enabled
// without magic, equivalent to "auto: () => true" instead of "auto: true"
//
// DEFAULT: "true" (i.e. path based magic)
auto: (resourcePath) => {
// Enable CSS modules unless the filename opts out using a file extension like "filename.global.scss"
return !/\.global\.\w+$/i.test(resourcePath);
},

// This setting has no effect unless CSS modules is enabled. Possible values:
// - "local": global CSS by default, overridable using the ":local" selector
// - "global": local CSS by default, overridable using the ":global" selector
// - "pure": requires selectors to contain at least one local class or id
// - a lambda that returns the mode string; the function parameter is the resource path
//
// DEFAULT: "local"
mode: 'local',

// Set this to true if you want to be able to reference the global declarations using import statements
// similar to local CSS modules
//
// DEFAULT: false
// exportGlobals: true,

// Provide a recognizable class/module names for developers
//
// DEFAULT: "[hash:base64]"
localIdentName: production ? '[hash:base64]' : '[local]__[hash:base64:5]'
},

sourceMap: !production
}
},

{
// PostCSS is a general-purpose CSS transformer; however, we prefer to avoid custom CSS syntaxes
// and only use the standard SASS syntax. Thus postcss-loader is used here only to apply the popular
// "autoprefixer" plugin improves browser compatibility by generating vendor prefixes.
// https://www.npmjs.com/package/postcss-loader
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
// https://www.npmjs.com/package/autoprefixer
autoprefixer
]
},

sourceMap: !production
}
},

{
// Compiles SASS syntax into CSS
// https://www.npmjs.com/package/sass-loader
loader: 'sass-loader',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bartvandenende-wm pointed out that this recipe will apply SASS transformations to .css files, which we don't want

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're thinking to change the rig to work like this:

  • .css : NO sass, NO autoprefixer, NO modules
  • .scss : sass, autoprefixer, modules
  • .global.scss : sass, autoprefixer, NO modules

...and if someone wants CSS with modules, they can simply use the .sass file extension, since SASS is basically a superset of CSS.

There's a small performance penalty for applying SASS to a plain CSS file, but @halfnibble pointed out that the extra syntax validation probably justifies that cost.

options: {
implementation: sass,
sassOptions: {
includePaths: [path.resolve(__dirname, 'node_modules')]
},

sourceMap: !production
}
}
]
},

{
test: /\.(jpeg|jpg|png|gif|svg|ico)$/,
// Allows import/require() to be used with an asset file. The file will be copied to the output folder,
// and the import statement will return its URL.
// https://www.npmjs.com/package/file-loader
loader: 'file-loader'
},

{
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader']
use: [
// The source-map-loader extracts existing source maps from all JavaScript entries. This includes both
// inline source maps as well as those linked via URL. All source map data is passed to Webpack for
// processing as per a chosen source map style specified by the devtool option in webpack.config.js.
// https://www.npmjs.com/package/source-map-loader
'source-map-loader'
]
}
]
},

entry: {
app: path.join(__dirname, 'lib', 'index.js'),

// Put these libraries in a separate vendor bundle
vendor: ['react', 'react-dom']
},

output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[contenthash].js'
},

performance: {
// This specifies the bundle size limit that will trigger Webpack's warning saying:
// "The following entrypoint(s) combined asset size exceeds the recommended limit."
maxEntrypointSize: 250000,
maxAssetSize: 250000
},

devServer: {
port: 9000
port: 9000,

// The http://localhost namespace should be relative to the "dist" folder
contentBase: path.join(__dirname, 'dist')
},

devtool: production ? undefined : 'source-map',
plugins: [
// See here for documentation: https://github.com/jantimon/html-webpack-plugin
new HtmlWebpackPlugin({
template: 'assets/index.html'
}),

// See here for documentation: https://webpack.js.org/plugins/define-plugin/
new DefinePlugin({
DEBUG: !production
})
]
};
Expand Down
Loading