improve Babel and webpack configurations --> reduce JS bundle size (#670)

* simplify babel config

* fix Cypress VSCode checks

* introduce webpack base config

* fix flacky cypress test

* fix class properties definition

* fix default export

* upgrade affected deps, decrease bundlesize

* run ESLint only on changed files
This commit is contained in:
Konstantin Vyatkin 2019-10-22 12:08:43 -04:00 committed by Josh Johnson
parent 00bf028904
commit 6848970fd9
13 changed files with 2712 additions and 5659 deletions

View file

@ -1,11 +1,3 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-modules-commonjs",
["@babel/plugin-transform-spread", {
"loose": true
}]
]
"presets": [["@babel/preset-env", { "loose": true }]]
}

View file

@ -25,4 +25,7 @@ jobs:
HUSKY_SKIP_INSTALL: true
- name: run eslint
run: npx eslint src/scripts
run: |
CHANGED_JS=$(git --no-pager diff --name-only ..origin/master | grep '^src\/scripts\/.*\.js$' | grep -v json)
echo $CHANGED_JS
node node_modules/eslint/bin/eslint.js $CHANGED_JS

View file

@ -1,5 +1,5 @@
# Choices.js [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Unit%20Tests/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![npm](https://img.shields.io/npm/v/choices.js.svg)](https://www.npmjs.com/package/choices.js) [![codebeat badge](https://codebeat.co/badges/55120150-5866-42d8-8010-6aaaff5d3fa1)](https://codebeat.co/projects/github-com-jshjohnson-choices-master)
A vanilla, lightweight (~22kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
A vanilla, lightweight (~19kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
[Demo](https://joshuajohnson.co.uk/Choices/)
@ -153,11 +153,11 @@ will be returned. If you target one element, that instance will be returned.
```
## Terminology
| Word | Definition |
| ------ | ---------- |
| Choice | A choice is a value a user can select. A choice would be equivalent to the `<option></option>` element within a select input. |
| Group | A group is a collection of choices. A group should be seen as equivalent to a `<optgroup></optgroup>` element within a select input.|
| Item | An item is an inputted value (text input) or a selected choice (select element). In the context of a select element, an item is equivalent to a selected option element: `<option value="Hello" selected></option>` whereas in the context of a text input an item is equivalent to `<input type="text" value="Hello">`|
| Word | Definition |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Choice | A choice is a value a user can select. A choice would be equivalent to the `<option></option>` element within a select input. |
| Group | A group is a collection of choices. A group should be seen as equivalent to a `<optgroup></optgroup>` element within a select input. |
| Item | An item is an inputted value (text input) or a selected choice (select element). In the context of a select element, an item is equivalent to a selected option element: `<option value="Hello" selected></option>` whereas in the context of a text input an item is equivalent to `<input type="text" value="Hello">` |
## Configuration options
@ -946,18 +946,18 @@ To setup a local environment: clone this repo, navigate into it's directory in a
```npm install```
### NPM tasks
| Task | Usage |
| -------------------- | ------------------------------------------------------------ |
| `npm run start` | Fire up local server for development |
| `npm run test:unit` | Run sequence of tests once |
| `npm run test:unit:watch` | Fire up test server and re-test on file change |
| `npm run test:e2e` | Run sequence of e2e tests (with local server) |
| `npm run test` | Run both unit and e2e tests |
| `npm run cypress:open` | Run Cypress e2e tests (GUI) |
| `npm run cypress:run` | Run Cypress e2e tests (CLI) |
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
| Task | Usage |
| ------------------------- | ------------------------------------------------------------ |
| `npm run start` | Fire up local server for development |
| `npm run test:unit` | Run sequence of tests once |
| `npm run test:unit:watch` | Fire up test server and re-test on file change |
| `npm run test:e2e` | Run sequence of e2e tests (with local server) |
| `npm run test` | Run both unit and e2e tests |
| `npm run cypress:open` | Run Cypress e2e tests (GUI) |
| `npm run cypress:run` | Run Cypress e2e tests (CLI) |
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
## License
MIT License

View file

@ -755,7 +755,7 @@ describe('Choices - select multiple', () => {
describe('within form', () => {
describe('selecting choice', () => {
describe('on enter key', () => {
it('does not submit form', () => {
it('selects choice', () => {
cy.get('[data-test-hook=within-form] form').then($form => {
$form.submit(() => {
// this will fail the test if the form submits
@ -765,7 +765,7 @@ describe('Choices - select multiple', () => {
cy.get('[data-test-hook=within-form]')
.find('.choices__input--cloned')
.focus()
.click()
.type('{enter}');
cy.get('[data-test-hook=within-form]')

9
jsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es2020",
"lib": ["es5", "dom"],
"types": ["cypress"]
}
}

8086
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -56,13 +56,10 @@
],
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
"@babel/plugin-transform-spread": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@babel/register": "^7.0.0",
"@babel/preset-env": "^7.6.3",
"@babel/register": "^7.6.2",
"autoprefixer": "^9.6.5",
"babel-loader": "^8.0.5",
"babel-loader": "^8.0.6",
"bundlesize": "^0.18.0",
"chai": "^4.2.0",
"csso": "^1.8.2",
@ -86,19 +83,18 @@
"postcss-cli": "^6.1.3",
"prettier": "^1.16.4",
"sinon": "^7.5.0",
"unminified-webpack-plugin": "^2.0.0",
"webpack": "^4.29.3",
"webpack-cli": "^3.2.3",
"webpack-dev-middleware": "^3.5.2",
"webpack-hot-middleware": "^2.24.3",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9",
"webpack-dev-middleware": "^3.7.2",
"webpack-hot-middleware": "^2.25.0",
"whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^2.1.0"
},
"dependencies": {
"classnames": "^2.2.6",
"deepmerge": "^2.2.1",
"deepmerge": "^4.2.0",
"fuse.js": "3.4.2",
"redux": "^3.3.1"
"redux": "^4.0.4"
},
"npmName": "choices.js",
"npmFileMap": [
@ -121,7 +117,7 @@
"bundlesize": [
{
"path": "public/assets/scripts/choices.min.js",
"maxSize": "25 kB"
"maxSize": "20 kB"
},
{
"path": "public/assets/styles/choices.min.css",

View file

@ -1706,9 +1706,7 @@ class Choices {
const { choices } = this._store;
const choiceLabel = label || value;
const choiceId = choices ? choices.length + 1 : 1;
const choiceElementId = `${this._baseId}-${
this._idNames.itemChoice
}-${choiceId}`;
const choiceElementId = `${this._baseId}-${this._idNames.itemChoice}-${choiceId}`;
this._store.dispatch(
addChoice({
@ -2086,5 +2084,5 @@ class Choices {
}
Choices.userDefaults = {};
// We cannot export default here due to Webpack: https://github.com/webpack/webpack/issues/3929
module.exports = Choices;
export default Choices;

View file

@ -15,6 +15,11 @@ export default class WrappedElement {
return this.element.value;
}
set value(value) {
// you must define setter here otherwise it will be readonly property
this.element.value = value;
}
conceal() {
// Hide passed input
this.element.classList.add(this.classNames.input);

View file

@ -14,8 +14,7 @@ export default class WrappedInput extends WrappedElement {
this.element.value = joinedValues;
}
// @todo figure out why we need this? Perhaps a babel issue
get value() {
return super.value;
return this.element.value;
}
}

39
webpack.config.base.js Normal file
View file

@ -0,0 +1,39 @@
const path = require('path');
const include = path.resolve(__dirname, './src/scripts');
const exclude = /node_modules/;
/**
* @type {import('webpack').Configuration}
*/
module.exports = {
entry: ['./src/scripts/choices'],
output: {
library: 'Choices',
libraryTarget: 'umd',
libraryExport: 'default',
},
module: {
rules: [
{
enforce: 'pre',
loader: 'eslint-loader',
test: /\.js?$/,
include,
exclude,
options: {
quiet: true,
},
},
{
loader: 'babel-loader',
test: /\.js?$/,
include,
exclude,
options: {
babelrc: true,
},
},
],
},
};

View file

@ -1,42 +1,23 @@
const path = require('path');
const { HotModuleReplacementPlugin } = require('webpack');
const deepMerge = require('deepmerge');
const baseConfig = require('./webpack.config.base');
module.exports = {
mode: 'development',
entry: [
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'./src/scripts/choices',
],
output: {
path: path.resolve('public'),
filename: 'choices.min.js',
publicPath: 'http://localhost:3001/assets/scripts/',
library: 'Choices',
libraryTarget: 'umd',
module.exports = deepMerge(
baseConfig,
{
mode: 'development',
output: {
path: path.resolve(__dirname, './public'),
filename: 'choices.min.js',
publicPath: 'http://localhost:3001/assets/scripts/',
},
entry: ['webpack/hot/dev-server', 'webpack-hot-middleware/client'],
plugins: [new HotModuleReplacementPlugin()],
},
plugins: [new HotModuleReplacementPlugin()],
module: {
rules: [
{
enforce: 'pre',
test: /\.js?$/,
include: path.join(__dirname, 'src/scripts'),
exclude: /node_modules/,
loader: 'eslint-loader',
query: {
configFile: '.eslintrc',
},
},
{
test: /\.js?$/,
include: path.join(__dirname, 'src/scripts'),
exclude: /node_modules/,
loader: 'babel-loader',
options: {
babelrc: true,
},
},
],
{
arrayMerge(target, source) {
return [...source, ...target];
},
},
};
);

View file

@ -1,56 +1,47 @@
const path = require('path');
const deepMerge = require('deepmerge');
const WrapperPlugin = require('wrapper-webpack-plugin');
const UnminifiedWebpackPlugin = require('unminified-webpack-plugin');
const pkg = require('./package.json');
const baseConfig = require('./webpack.config.base');
const { name, version, author, homepage } = require('./package.json');
const banner = `/*! ${pkg.name} v${
pkg.version
} | (c) ${new Date().getFullYear()} ${pkg.author} | ${pkg.homepage} */ \n`;
const arrayMerge = (target, source) => [...source, ...target];
module.exports = {
mode: 'production',
entry: ['./src/scripts/choices'],
output: {
path: path.join(__dirname, '/public/assets/scripts'),
filename: 'choices.min.js',
publicPath: '/public/assets/scripts/',
library: 'Choices',
libraryTarget: 'umd',
auxiliaryComment: {
root: 'Window',
commonjs: 'CommonJS',
commonjs2: 'CommonJS2',
amd: 'AMD',
const prodConfig = deepMerge(
baseConfig,
{
mode: 'production',
output: {
path: path.join(__dirname, '/public/assets/scripts'),
publicPath: '/public/assets/scripts/',
},
},
plugins: [
new WrapperPlugin({
header: banner,
}),
new UnminifiedWebpackPlugin(),
],
module: {
rules: [
{
enforce: 'pre',
test: /\.js?$/,
include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/,
loader: 'eslint-loader',
query: {
configFile: '.eslintrc',
},
},
{
test: /\.js?$/,
include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
options: {
babelrc: true,
},
},
plugins: [
new WrapperPlugin({
header: `/*! ${name} v${version} | © ${new Date().getFullYear()} ${author} | ${homepage} */ \n`,
}),
],
},
};
{
arrayMerge,
},
);
module.exports = [
deepMerge(
prodConfig,
{
output: { filename: 'choices.js' },
optimization: { minimize: false },
},
{
arrayMerge,
},
),
deepMerge(
prodConfig,
{ output: { filename: 'choices.min.js' } },
{
arrayMerge,
},
),
];