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": [ "presets": [["@babel/preset-env", { "loose": true }]]
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-modules-commonjs",
["@babel/plugin-transform-spread", {
"loose": true
}]
]
} }

View file

@ -25,4 +25,7 @@ jobs:
HUSKY_SKIP_INSTALL: true HUSKY_SKIP_INSTALL: true
- name: run eslint - 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) # 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/) [Demo](https://joshuajohnson.co.uk/Choices/)
@ -153,11 +153,11 @@ will be returned. If you target one element, that instance will be returned.
``` ```
## Terminology ## Terminology
| Word | Definition | | 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. | | 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.| | 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">`| | 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 ## Configuration options
@ -946,18 +946,18 @@ To setup a local environment: clone this repo, navigate into it's directory in a
```npm install``` ```npm install```
### NPM tasks ### NPM tasks
| Task | Usage | | Task | Usage |
| -------------------- | ------------------------------------------------------------ | | ------------------------- | ------------------------------------------------------------ |
| `npm run start` | Fire up local server for development | | `npm run start` | Fire up local server for development |
| `npm run test:unit` | Run sequence of tests once | | `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: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:e2e` | Run sequence of e2e tests (with local server) |
| `npm run test` | Run both unit and e2e tests | | `npm run test` | Run both unit and e2e tests |
| `npm run cypress:open` | Run Cypress e2e tests (GUI) | | `npm run cypress:open` | Run Cypress e2e tests (GUI) |
| `npm run cypress:run` | Run Cypress e2e tests (CLI) | | `npm run cypress:run` | Run Cypress e2e tests (CLI) |
| `npm run js:build` | Compile Choices to an uglified JavaScript file | | `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:watch` | Watch SCSS files for changes. On a change, run build process |
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS | | `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
## License ## License
MIT License MIT License

View file

@ -755,7 +755,7 @@ describe('Choices - select multiple', () => {
describe('within form', () => { describe('within form', () => {
describe('selecting choice', () => { describe('selecting choice', () => {
describe('on enter key', () => { describe('on enter key', () => {
it('does not submit form', () => { it('selects choice', () => {
cy.get('[data-test-hook=within-form] form').then($form => { cy.get('[data-test-hook=within-form] form').then($form => {
$form.submit(() => { $form.submit(() => {
// this will fail the test if the form submits // this will fail the test if the form submits
@ -765,7 +765,7 @@ describe('Choices - select multiple', () => {
cy.get('[data-test-hook=within-form]') cy.get('[data-test-hook=within-form]')
.find('.choices__input--cloned') .find('.choices__input--cloned')
.focus() .click()
.type('{enter}'); .type('{enter}');
cy.get('[data-test-hook=within-form]') 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": { "devDependencies": {
"@babel/core": "^7.6.4", "@babel/core": "^7.6.4",
"@babel/plugin-proposal-class-properties": "^7.3.0", "@babel/preset-env": "^7.6.3",
"@babel/plugin-transform-modules-commonjs": "^7.2.0", "@babel/register": "^7.6.2",
"@babel/plugin-transform-spread": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@babel/register": "^7.0.0",
"autoprefixer": "^9.6.5", "autoprefixer": "^9.6.5",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.6",
"bundlesize": "^0.18.0", "bundlesize": "^0.18.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"csso": "^1.8.2", "csso": "^1.8.2",
@ -86,19 +83,18 @@
"postcss-cli": "^6.1.3", "postcss-cli": "^6.1.3",
"prettier": "^1.16.4", "prettier": "^1.16.4",
"sinon": "^7.5.0", "sinon": "^7.5.0",
"unminified-webpack-plugin": "^2.0.0", "webpack": "^4.41.2",
"webpack": "^4.29.3", "webpack-cli": "^3.3.9",
"webpack-cli": "^3.2.3", "webpack-dev-middleware": "^3.7.2",
"webpack-dev-middleware": "^3.5.2", "webpack-hot-middleware": "^2.25.0",
"webpack-hot-middleware": "^2.24.3",
"whatwg-fetch": "^1.0.0", "whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^2.1.0" "wrapper-webpack-plugin": "^2.1.0"
}, },
"dependencies": { "dependencies": {
"classnames": "^2.2.6", "classnames": "^2.2.6",
"deepmerge": "^2.2.1", "deepmerge": "^4.2.0",
"fuse.js": "3.4.2", "fuse.js": "3.4.2",
"redux": "^3.3.1" "redux": "^4.0.4"
}, },
"npmName": "choices.js", "npmName": "choices.js",
"npmFileMap": [ "npmFileMap": [
@ -121,7 +117,7 @@
"bundlesize": [ "bundlesize": [
{ {
"path": "public/assets/scripts/choices.min.js", "path": "public/assets/scripts/choices.min.js",
"maxSize": "25 kB" "maxSize": "20 kB"
}, },
{ {
"path": "public/assets/styles/choices.min.css", "path": "public/assets/styles/choices.min.css",

View file

@ -1706,9 +1706,7 @@ class Choices {
const { choices } = this._store; const { choices } = this._store;
const choiceLabel = label || value; const choiceLabel = label || value;
const choiceId = choices ? choices.length + 1 : 1; const choiceId = choices ? choices.length + 1 : 1;
const choiceElementId = `${this._baseId}-${ const choiceElementId = `${this._baseId}-${this._idNames.itemChoice}-${choiceId}`;
this._idNames.itemChoice
}-${choiceId}`;
this._store.dispatch( this._store.dispatch(
addChoice({ addChoice({
@ -2086,5 +2084,5 @@ class Choices {
} }
Choices.userDefaults = {}; 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; return this.element.value;
} }
set value(value) {
// you must define setter here otherwise it will be readonly property
this.element.value = value;
}
conceal() { conceal() {
// Hide passed input // Hide passed input
this.element.classList.add(this.classNames.input); this.element.classList.add(this.classNames.input);

View file

@ -14,8 +14,7 @@ export default class WrappedInput extends WrappedElement {
this.element.value = joinedValues; this.element.value = joinedValues;
} }
// @todo figure out why we need this? Perhaps a babel issue
get value() { 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 path = require('path');
const { HotModuleReplacementPlugin } = require('webpack'); const { HotModuleReplacementPlugin } = require('webpack');
const deepMerge = require('deepmerge');
const baseConfig = require('./webpack.config.base');
module.exports = { module.exports = deepMerge(
mode: 'development', baseConfig,
entry: [ {
'webpack/hot/dev-server', mode: 'development',
'webpack-hot-middleware/client', output: {
'./src/scripts/choices', path: path.resolve(__dirname, './public'),
], filename: 'choices.min.js',
output: { publicPath: 'http://localhost:3001/assets/scripts/',
path: path.resolve('public'), },
filename: 'choices.min.js', entry: ['webpack/hot/dev-server', 'webpack-hot-middleware/client'],
publicPath: 'http://localhost:3001/assets/scripts/', plugins: [new HotModuleReplacementPlugin()],
library: 'Choices',
libraryTarget: 'umd',
}, },
plugins: [new HotModuleReplacementPlugin()], {
module: { arrayMerge(target, source) {
rules: [ return [...source, ...target];
{ },
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,
},
},
],
}, },
}; );

View file

@ -1,56 +1,47 @@
const path = require('path'); const path = require('path');
const deepMerge = require('deepmerge');
const WrapperPlugin = require('wrapper-webpack-plugin'); 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${ const arrayMerge = (target, source) => [...source, ...target];
pkg.version
} | (c) ${new Date().getFullYear()} ${pkg.author} | ${pkg.homepage} */ \n`;
module.exports = { const prodConfig = deepMerge(
mode: 'production', baseConfig,
entry: ['./src/scripts/choices'], {
output: { mode: 'production',
path: path.join(__dirname, '/public/assets/scripts'), output: {
filename: 'choices.min.js', path: path.join(__dirname, '/public/assets/scripts'),
publicPath: '/public/assets/scripts/', publicPath: '/public/assets/scripts/',
library: 'Choices',
libraryTarget: 'umd',
auxiliaryComment: {
root: 'Window',
commonjs: 'CommonJS',
commonjs2: 'CommonJS2',
amd: 'AMD',
}, },
}, plugins: [
plugins: [ new WrapperPlugin({
new WrapperPlugin({ header: `/*! ${name} v${version} | © ${new Date().getFullYear()} ${author} | ${homepage} */ \n`,
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,
},
},
], ],
}, },
}; {
arrayMerge,
},
);
module.exports = [
deepMerge(
prodConfig,
{
output: { filename: 'choices.js' },
optimization: { minimize: false },
},
{
arrayMerge,
},
),
deepMerge(
prodConfig,
{ output: { filename: 'choices.min.js' } },
{
arrayMerge,
},
),
];