diff --git a/.circleci/config.yml b/.circleci/config.yml index 761cbb6302..aa8b4bbf10 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,19 @@ -version: 2 +version: 2.1 defaults: &defaults working_directory: ~/project/vue docker: - - image: vuejs/ci + - image: circleci/node:lts-browsers + +aliases: + - &restore-yarn-cache + key: v2-vue-cli-{{ checksum "yarn.lock" }} + + - &save-yarn-cache + key: v2-vue-cli-{{ checksum "yarn.lock" }} + paths: + - node_modules/ + - ~/.cache workflow_filters: &filters filters: @@ -16,45 +26,46 @@ jobs: <<: *defaults steps: - checkout - - restore_cache: - keys: - - v2-vue-cli-{{ .Branch }}-{{ checksum "yarn.lock" }} + - restore_cache: *restore-yarn-cache - run: yarn --network-timeout 600000 - - save_cache: - key: v2-vue-cli-{{ .Branch }}-{{ checksum "yarn.lock" }} - paths: - - node_modules/ - - ~/.cache + - save_cache: *save-yarn-cache - persist_to_workspace: root: ~/ paths: - project/vue - .cache/Cypress - group-1: + e2e: + <<: *defaults + steps: + - attach_workspace: + at: ~/ + - run: ./scripts/e2e-test/run-e2e-test.sh + + core: <<: *defaults steps: - attach_workspace: at: ~/ - run: yarn test -p cli,cli-service,cli-shared-utils - group-2: + typescript: <<: *defaults steps: - attach_workspace: at: ~/ - run: yarn test 'ts(?:\w(?!E2e))+\.spec\.js$' - group-3: + plugins: <<: *defaults steps: - attach_workspace: at: ~/ - - run: yarn lint + - run: yarn lint-without-fix - run: yarn check-links - - run: yarn test -p cli-service-global,eslint,pwa,babel,babel-preset-app,vuex,router + - run: yarn test -p eslint,pwa,babel,babel-preset-app,vuex,router - group-4: + tests: <<: *defaults steps: - attach_workspace: @@ -73,6 +84,8 @@ jobs: path: packages/@vue/cli-ui/tests/e2e/videos - store_artifacts: path: packages/@vue/cli-ui/tests/e2e/screenshots + - store_artifacts: + path: /home/circleci/.npm/_logs workflows: version: 2 @@ -80,19 +93,19 @@ workflows: jobs: - install: <<: *filters - - group-1: + - core: <<: *filters requires: - install - - group-2: + - typescript: <<: *filters requires: - install - - group-3: + - plugins: <<: *filters requires: - install - - group-4: + - tests: <<: *filters requires: - install @@ -100,3 +113,7 @@ workflows: <<: *filters requires: - install + - e2e: + <<: *filters + requires: + - install diff --git a/.eslintignore b/.eslintignore index 7cd225a07e..057b28cb82 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,7 @@ node_modules template +template-vue3 packages/test temp -entry-wc.js dist +__testfixtures__ diff --git a/.eslintrc.js b/.eslintrc.js index cf64baee85..17c1d2df20 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,33 +1,32 @@ module.exports = { extends: [ - "plugin:vue-libs/recommended" + '@vue/standard' ], - plugins: [ - "node" - ], - env: { - "jest": true - }, globals: { name: 'off' }, rules: { - "indent": ["error", 2, { - "MemberExpression": "off" + indent: ['error', 2, { + MemberExpression: 'off' }], - "no-shadow": ["error"], - "node/no-extraneous-require": ["error", { - "allowModules": [ - "@vue/cli-service", - "@vue/cli-test-utils" + quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], + 'quote-props': 'off', + 'no-shadow': ['error'], + 'node/no-extraneous-require': ['error', { + allowModules: [ + '@vue/cli-service', + '@vue/cli-test-utils' ] }] }, overrides: [ { - files: ['**/__tests__/**/*.js', "**/cli-test-utils/**/*.js"], + files: ['**/__tests__/**/*.js', '**/cli-test-utils/**/*.js'], + env: { + jest: true + }, rules: { - "node/no-extraneous-require": "off" + 'node/no-extraneous-require': 'off' } } ] diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5eb8134fb0..66d44970a9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,3 +1,5 @@ +# Contributing to Vue CLI + ## Workflow The Git workflow used in this project is largely inspired by [Gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). @@ -24,6 +26,7 @@ yarn # if you have the old vue-cli installed globally, you may # need to uninstall it first. cd packages/@vue/cli +# before yarn link, you can delete the link in `~/.config/yarn/link/@vue` (see issue: [yarn link error message is not helpful](https://github.com/yarnpkg/yarn/issues/7054)) yarn link # create test projects in /packages/test @@ -66,4 +69,4 @@ Note that `jest --onlyChanged` isn't always accurate because some tests spawn ch ### Plugin Development -See [dedicated section in docs](https://github.com/vuejs/vue-cli/blob/dev/docs/dev-guide/plugin-dev.md). +See [dedicated section in docs](https://cli.vuejs.org/dev-guide/plugin-dev.html). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..fcb1245b51 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +github: [yyx990803, sodatea] +patreon: evanyou +open_collective: vuejs +tidelift: npm/vue diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 601834beb7..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,8 +0,0 @@ -IMPORTANT: Please use the following link to create a new issue: - -https://new-issue.vuejs.org/?repo=vuejs/vue-cli - -If your issue was not created using the app above, it will be closed immediately. - -中文用户请注意: -请使用上面的链接来创建新的 issue。如果不是用上述工具创建的 issue 会被自动关闭。 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..863885d6fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Create new issue + url: https://new-issue.vuejs.org/?repo=vuejs/vue-cli + about: Please use the following link to create a new issue. + - name: Patreon + url: https://www.patreon.com/evanyou + about: Love Vue.js? Please consider supporting us via Patreon. + - name: Open Collective + url: https://opencollective.com/vuejs/donate + about: Love Vue.js? Please consider supporting us via Open Collective. diff --git a/.gitignore b/.gitignore index 3a87b1b4e3..03a51326dd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ temp .version .versions .changelog +package-lock.json +.vscode diff --git a/.postcssrc b/.postcssrc deleted file mode 100644 index ed0149bf8b..0000000000 --- a/.postcssrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "plugins": { - "autoprefixer": {} - } -} \ No newline at end of file diff --git a/.tidelift.yml b/.tidelift.yml new file mode 100644 index 0000000000..44f682c3d7 --- /dev/null +++ b/.tidelift.yml @@ -0,0 +1,2 @@ +tests: + unlicensed: warn diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac8e7f8e7..84c1fb2de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,1931 @@ + + + +## 5.0.7 (2022-07-05) + +* `@vue/cli-service` + * [#7202](https://github.com/vuejs/vue-cli/pull/7202), [[558dea2](https://github.com/vuejs/vue-cli/commit/558dea2)] fix: support `devServer.server` option, avoid deprecation warnings ([@backrunner](https://github.com/backrunner), [@sodatea](https://github.com/sodatea)) + * [[beffe8a](https://github.com/vuejs/vue-cli/commit/beffe8a)] fix: allow disabling progress plugin via `devServer.client.progress` +* `@vue/cli-ui` + * [#7210](https://github.com/vuejs/vue-cli/pull/7210) chore: upgrade to apollo-server-express 3.x + +#### Committers: 2 +- BackRunner ([@backrunner](https://github.com/backrunner)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 5.0.6 (2022-06-16) + +Fix compatibility with the upcoming Vue 2.7 (currently in alpha) and Vue Loader 15.10 (currently in beta). + +In Vue 2.7, `vue-template-compiler` is no longer a required peer dependency. Rather, there's a new export under the main package as `vue/compiler-sfc`. + + + +## 5.0.5 (2022-06-16) + +#### :bug: Bug Fix +* `@vue/cli` + * [#7167](https://github.com/vuejs/vue-cli/pull/7167) feat(upgrade): prevent changing the structure of package.json file during upgrade ([@blzsaa](https://github.com/blzsaa)) +* `@vue/cli-service` + * [#7023](https://github.com/vuejs/vue-cli/pull/7023) fix: windows vue.config.mjs support ([@xiaoxiangmoe](https://github.com/xiaoxiangmoe)) + +#### Committers: 3 +- Martijn Jacobs ([@maerteijn](https://github.com/maerteijn)) +- ZHAO Jinxiang ([@xiaoxiangmoe](https://github.com/xiaoxiangmoe)) +- [@blzsaa](https://github.com/blzsaa) + + + +## 5.0.4 (2022-03-22) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#7005](https://github.com/vuejs/vue-cli/pull/7005) Better handling of `publicPath: 'auto'` ([@AndreiSoroka](https://github.com/AndreiSoroka)) +* `@vue/cli-shared-utils`, `@vue/cli-ui` + * [75826d6](https://github.com/vuejs/vue-cli/commit/75826d6) fix: replace `node-ipc` with `@achrinza/node-ipc` to further secure the dependency chain + +#### Committers: 1 +- Andrei ([@AndreiSoroka](https://github.com/AndreiSoroka)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + +## 5.0.3 (2022-03-15) + +#### :bug: Bug Fix +* `@vue/cli-shared-utils`, `@vue/cli-ui` + * Lock `node-ipc` to v9.2.1 + +## 5.0.2 (2022-03-15) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#7044](https://github.com/vuejs/vue-cli/pull/7044) fix(cli-service): devServer proxy should be optional ([@ntnyq](https://github.com/ntnyq)) + * [#7039](https://github.com/vuejs/vue-cli/pull/7039) chore: add scss to LoaderOptions ([@hiblacker](https://github.com/hiblacker)) + +#### Committers: 2 +- Blacker ([@hiblacker](https://github.com/hiblacker)) +- ntnyq ([@ntnyq](https://github.com/ntnyq)) + + +## 5.0.1 (2022-02-17) + +Same as 5.0.0. + +## 5.0.0 (2022-02-17) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6972](https://github.com/vuejs/vue-cli/pull/6972) Remove --skip-plugin from arguments ([@MatthijsBurgh](https://github.com/MatthijsBurgh)) + * [#6987](https://github.com/vuejs/vue-cli/pull/6987) fix: update mini-css-extract-plugin to ^2.5.3 ([@darrinmn9](https://github.com/darrinmn9)) + +#### :memo: Documentation +* [#6706](https://github.com/vuejs/vue-cli/pull/6706) docs: update vue create --help output in "Basics/Creating a Project" ([@Lalaluka](https://github.com/Lalaluka)) +* [#6642](https://github.com/vuejs/vue-cli/pull/6642) docs: Update README.md ([@wxsms](https://github.com/wxsms)) +* [#6620](https://github.com/vuejs/vue-cli/pull/6620) Fix typo in deployment guide ([@Klikini](https://github.com/Klikini)) +* [#6623](https://github.com/vuejs/vue-cli/pull/6623) fix(docs): the plugin-dev in zh has a regexp lose the end / ([@HelloJiya](https://github.com/HelloJiya)) +* [#6377](https://github.com/vuejs/vue-cli/pull/6377) replace master with main to reflect GH default ([@anbnyc](https://github.com/anbnyc)) +* [#6359](https://github.com/vuejs/vue-cli/pull/6359) Fix master to main in heroku deployment ([@MowlCoder](https://github.com/MowlCoder)) +* [#6266](https://github.com/vuejs/vue-cli/pull/6266) Add note about loader incompatible with webpack 4 ([@JarnoRFB](https://github.com/JarnoRFB)) +* [#6239](https://github.com/vuejs/vue-cli/pull/6239) Update deployment.md ([@anzuj](https://github.com/anzuj)) +* [#6237](https://github.com/vuejs/vue-cli/pull/6237) fix code demo ([@yyzclyang](https://github.com/yyzclyang)) + +#### Committers: 13 +- Alec Barrett ([@anbnyc](https://github.com/anbnyc)) +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Andy Castille ([@Klikini](https://github.com/Klikini)) +- Anzelika ([@anzuj](https://github.com/anzuj)) +- Ben Hutton ([@Relequestual](https://github.com/Relequestual)) +- Calvin Schröder ([@Lalaluka](https://github.com/Lalaluka)) +- Darrin Nagengast ([@darrinmn9](https://github.com/darrinmn9)) +- Matthijs van der Burgh ([@MatthijsBurgh](https://github.com/MatthijsBurgh)) +- Rüdiger Busche ([@JarnoRFB](https://github.com/JarnoRFB)) +- [@HelloJiya](https://github.com/HelloJiya) +- [@MowlCoder](https://github.com/MowlCoder) +- wxsm ([@wxsms](https://github.com/wxsms)) +- 鱼依藻常乐 ([@yyzclyang](https://github.com/yyzclyang)) + + + +## 5.0.0-rc.3 (2022-02-10) + +#### :rocket: New Features +* `@vue/cli-service` + * [#6980](https://github.com/vuejs/vue-cli/pull/6980) feat: add build stats hash support ([@xiaoxiangmoe](https://github.com/xiaoxiangmoe)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#6520](https://github.com/vuejs/vue-cli/pull/6520) feat: Upgraded Nightwatch to 2.0, updated distribued config ([@vaibhavsingh97](https://github.com/vaibhavsingh97)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#6985](https://github.com/vuejs/vue-cli/pull/6985) feat!: make `cache-loader` optional ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#6520](https://github.com/vuejs/vue-cli/pull/6520) feat: Upgraded Nightwatch to 2.0, updated distribued config ([@vaibhavsingh97](https://github.com/vaibhavsingh97)) + +#### :bug: Bug Fix +* `@vue/cli-ui` + * [#6969](https://github.com/vuejs/vue-cli/pull/6969) fix: remove non standard rel=shortcut ([@Rotzbua](https://github.com/Rotzbua)) + +#### Committers: 6 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Rotzbua ([@Rotzbua](https://github.com/Rotzbua)) +- Simon Stieger ([@sstieger](https://github.com/sstieger)) +- Vaibhav Singh ([@vaibhavsingh97](https://github.com/vaibhavsingh97)) +- ZHAO Jinxiang ([@xiaoxiangmoe](https://github.com/xiaoxiangmoe)) +- [@DarknessChaser](https://github.com/DarknessChaser) + + + +## 5.0.0-rc.2 (2022-01-15) + +#### :rocket: New Features +* `@vue/cli-ui`, `@vue/cli` + * [#6917](https://github.com/vuejs/vue-cli/pull/6917) feat!: make Vue 3 the default version for `vue create` ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-ui`, `@vue/cli` + * [#6917](https://github.com/vuejs/vue-cli/pull/6917) feat!: make Vue 3 the default version for `vue create` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6872](https://github.com/vuejs/vue-cli/pull/6872) chore: use vue-loader v17 ([@cexbrayat](https://github.com/cexbrayat)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6944](https://github.com/vuejs/vue-cli/pull/6944) fix: set mini-css-extract-plugin to 2.4.5 ([@cexbrayat](https://github.com/cexbrayat)) + * [#6907](https://github.com/vuejs/vue-cli/pull/6907) fix: use `setupMiddlewares`, avoid dev server deprecation warnings ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#6926](https://github.com/vuejs/vue-cli/pull/6926) fix: Update cypress api link to the latest ([@justforuse](https://github.com/justforuse)) + +#### Committers: 3 +- Allen ([@justforuse](https://github.com/justforuse)) +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 5.0.0-rc.1 (2021-11-17) + +#### :rocket: New Features +* `@vue/cli` + * [#6824](https://github.com/vuejs/vue-cli/pull/6824) feat: update npm.taobao.org to npmmirror.com ([@Certseeds](https://github.com/Certseeds)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6826](https://github.com/vuejs/vue-cli/pull/6826) fix: [ext] in asset modules already contains a leading dot ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#6829](https://github.com/vuejs/vue-cli/pull/6829) fix: require webpack 5.54+ ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex` + * [#6821](https://github.com/vuejs/vue-cli/pull/6821) docs: replace vuepress with vitepress ([@sodatea](https://github.com/sodatea)) + +#### Committers: 3 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Killer_Quinn ([@Certseeds](https://github.com/Certseeds)) +- puxiao ([@puxiao](https://github.com/puxiao)) + + + +## 5.0.0-rc.0 (2021-11-06) + +#### :rocket: New Features +* `@vue/cli` + * [#6817](https://github.com/vuejs/vue-cli/pull/6817) feat: generate `vue.config.js` with `defineConfig` wrapper ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#6795](https://github.com/vuejs/vue-cli/pull/6795) feat(generator)!: bump eslint-plugin-vue to v8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6790](https://github.com/vuejs/vue-cli/pull/6790) feat!: bump css-loader and mini-css-extract-plugin versions ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint` + * [#6791](https://github.com/vuejs/vue-cli/pull/6791) feat: replace`@vue/eslint-config-prettier` with `eslint-config-prettier` ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/babel-preset-app`, `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#6808](https://github.com/vuejs/vue-cli/pull/6808) feat!: remove `@vue/compiler-sfc` from peer dependencies ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6790](https://github.com/vuejs/vue-cli/pull/6790) feat!: bump css-loader and mini-css-extract-plugin versions ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-unit-jest` + * [#6794](https://github.com/vuejs/vue-cli/pull/6794) fix(migrator): be aware of the project's vue version ([@stefanlivens](https://github.com/stefanlivens)) +* `@vue/cli-plugin-eslint` + * [#6787](https://github.com/vuejs/vue-cli/pull/6787) fix: bump eslint-webpack-plugin and fix lintOnError regressions ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#6809](https://github.com/vuejs/vue-cli/pull/6809) refactor: use multi-word names for router views ([@sodatea](https://github.com/sodatea)) + +#### Committers: 3 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Simon Legner ([@simon04](https://github.com/simon04)) +- [@stefanlivens](https://github.com/stefanlivens) + + + +## 5.0.0-beta.7 (2021-10-26) + +#### :rocket: New Features +* `@vue/cli-service` + * [#6771](https://github.com/vuejs/vue-cli/pull/6771) feat!: remove url-loader and file-loader in favor of asset modules ([@sodatea](https://github.com/sodatea)) + * [#6752](https://github.com/vuejs/vue-cli/pull/6752) Add a top-level `terser` option to allow users to customize the minifier ([@screetBloom](https://github.com/screetBloom)) + +#### :boom: Breaking Changes +* `@vue/cli-service` + * [#6781](https://github.com/vuejs/vue-cli/pull/6781) fix!: set hashFunction to `xxhash64` to fix Node 17 compatibility ([@sodatea](https://github.com/sodatea)) + * [#6771](https://github.com/vuejs/vue-cli/pull/6771) feat!: remove url-loader and file-loader in favor of asset modules ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6781](https://github.com/vuejs/vue-cli/pull/6781) fix!: set hashFunction to `xxhash64` to fix Node 17 compatibility ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest` + * [#6775](https://github.com/vuejs/vue-cli/pull/6775) fix(migrator): fix invalid semver ([@stefanlivens](https://github.com/stefanlivens)) + +#### Committers: 3 +- FM ([@screetBloom](https://github.com/screetBloom)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- [@stefanlivens](https://github.com/stefanlivens) + + + +## 5.0.0-beta.6 (2021-10-14) + +#### :rocket: New Features +* `@vue/cli-plugin-eslint`, `@vue/cli-service` + * [#6748](https://github.com/vuejs/vue-cli/pull/6748) feat: switch to stylish formatter for eslint ([@cexbrayat](https://github.com/cexbrayat)) + +#### Committers: 1 +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) + +#### Security Fixes + +This version fixed a CORS vulnerability and an XSS vulnerability in Vue CLI UI. +We recommend all users of `vue ui` to upgrade to this version as soon as possible. + +#### Credits: +Ngo Wei Lin ([@Creastery](https://twitter.com/creastery)) of STAR Labs ([@starlabs_sg](https://twitter.com/starlabs_sg)) + + +## 5.0.0-beta.5 (2021-10-10) + +#### :rocket: New Features +* `@vue/cli-plugin-eslint`, `@vue/cli-service` + * [#6714](https://github.com/vuejs/vue-cli/pull/6714) feat(cli-plugin-eslint): use ESLint class instead of CLIEngine ([@ota-meshi](https://github.com/ota-meshi)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-e2e-webdriverio` + * [#6695](https://github.com/vuejs/vue-cli/pull/6695) refactor(webdriverio)!: don't include sync API support by default ([@sodatea](https://github.com/sodatea)) + +#### Committers: 3 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Yosuke Ota ([@ota-meshi](https://github.com/ota-meshi)) +- [@zj9495](https://github.com/zj9495) + + + +## 5.0.0-beta.4 (2021-09-15) + +#### :rocket: New Features +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest` + * [#6627](https://github.com/vuejs/vue-cli/pull/6627) feat: update jest to v27 ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli-plugin-eslint`, `@vue/cli-service`, `@vue/cli-test-utils` + * [#6669](https://github.com/vuejs/vue-cli/pull/6669) feat!: upgrade to webpack-dev-server v4 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-eslint` + * [#6663](https://github.com/vuejs/vue-cli/pull/6663) feat: generate projects with `transpileDependencies: true` by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#6662](https://github.com/vuejs/vue-cli/pull/6662) feat!: update cypress to 8.3 and require it to be a peer dependency ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest` + * [#6627](https://github.com/vuejs/vue-cli/pull/6627) feat: update jest to v27 ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli-plugin-eslint`, `@vue/cli-service`, `@vue/cli-test-utils` + * [#6669](https://github.com/vuejs/vue-cli/pull/6669) feat!: upgrade to webpack-dev-server v4 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#6662](https://github.com/vuejs/vue-cli/pull/6662) feat!: update cypress to 8.3 and require it to be a peer dependency ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6665](https://github.com/vuejs/vue-cli/pull/6665) fix: avoid copy-webpack plugin errors in module mode ([@sodatea](https://github.com/sodatea)) + * [#6645](https://github.com/vuejs/vue-cli/pull/6645) fix(cli-service): wrong property name (typo) ([@Vinsea](https://github.com/Vinsea)) + +#### :memo: Documentation +* [#6653](https://github.com/vuejs/vue-cli/pull/6653) docs: recommend SFC playground and StackBlitz for instant prototyping ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-plugin-pwa`, `@vue/cli-service` + * [#6638](https://github.com/vuejs/vue-cli/pull/6638) refactor: remove redundant Webpack version checks ([@KubesDavid](https://github.com/KubesDavid)) +* `@vue/cli-ui` + * [#6635](https://github.com/vuejs/vue-cli/pull/6635) fix(ui): stop depending on the `watch` package ([@sodatea](https://github.com/sodatea)) + +#### Committers: 4 +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- David Kubeš ([@KubesDavid](https://github.com/KubesDavid)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Vinsea ([@Vinsea](https://github.com/Vinsea)) + + + +## 5.0.0-beta.3 (2021-08-10) + +#### :rocket: New Features +* `@vue/cli-plugin-unit-jest` + * [#6625](https://github.com/vuejs/vue-cli/pull/6625) feat(unit-jest): add jest as a peer dependency ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6530](https://github.com/vuejs/vue-cli/pull/6530) feat(cli-service): add support new image format avif ([@muhamadamin1992](https://github.com/muhamadamin1992)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6598](https://github.com/vuejs/vue-cli/pull/6598) chore!: drop webpack-4 support in v5 ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6597](https://github.com/vuejs/vue-cli/pull/6597) fix: mark `sideEffects: true` for styles in Vue components ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#6560](https://github.com/vuejs/vue-cli/pull/6560) fix(mocha): do not ignore JavaScript tests when TypeScript plugin is installed ([@j-a-m-l](https://github.com/j-a-m-l)) + +#### :memo: Documentation +* `@vue/cli` + * [#6589](https://github.com/vuejs/vue-cli/pull/6589) Fix command description typo ([@martiliones](https://github.com/martiliones)) + +#### Committers: 4 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Juan ([@j-a-m-l](https://github.com/j-a-m-l)) +- Muhammadamin ([@muhamadamin1992](https://github.com/muhamadamin1992)) +- martiliones ([@martiliones](https://github.com/martiliones)) + + + +## 5.0.0-beta.2 (2021-06-09) + +#### :rocket: New Features +* `@vue/cli-plugin-typescript`, `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli` + * [#6411](https://github.com/vuejs/vue-cli/pull/6411) feat: implement plugin execution order ([@fangbinwei](https://github.com/fangbinwei)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-pwa` + * [#6518](https://github.com/vuejs/vue-cli/pull/6518) fix(pwa): Replace closeTag parameter with voidTag for HtmlWebpackPlugin ([@tcitworld](https://github.com/tcitworld)) +* `@vue/cli-service` + * [#6506](https://github.com/vuejs/vue-cli/pull/6506) fix(webpack): slash on publicPath: 'auto' ([@tomicakr](https://github.com/tomicakr)) +* `@vue/cli-plugin-unit-mocha` + * [#6478](https://github.com/vuejs/vue-cli/pull/6478) fix(mocha): set mode to `none` to avoid DefinePlugin conflict ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#6493](https://github.com/vuejs/vue-cli/pull/6493) Fixed some minor typos ([@Ashikpaul](https://github.com/Ashikpaul)) +* [#6487](https://github.com/vuejs/vue-cli/pull/6487) update deployment.md ([@andydodo](https://github.com/andydodo)) + +#### :house: Internal +* `@vue/cli-service` + * [#6519](https://github.com/vuejs/vue-cli/pull/6519) chore: use scoped package names for aliases ([@sodatea](https://github.com/sodatea)) + +#### Committers: 6 +- Andy Do ([@andydodo](https://github.com/andydodo)) +- Ashik Paul ([@Ashikpaul](https://github.com/Ashikpaul)) +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Thomas Citharel ([@tcitworld](https://github.com/tcitworld)) +- tomica ([@tomicakr](https://github.com/tomicakr)) + + + +## 5.0.0-beta.1 (2021-05-14) + +#### :rocket: New Features +* `@vue/cli-service` + * [#6472](https://github.com/vuejs/vue-cli/pull/6472) Feature: add "tags" part to htmlWebpackPlugin ([@TimmersThomas](https://github.com/TimmersThomas)) +* `@vue/cli-plugin-unit-mocha` + * [#6471](https://github.com/vuejs/vue-cli/pull/6471) feat: support webpack 5 in unit-mocha plugin ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-ui` + * [#6443](https://github.com/vuejs/vue-cli/pull/6443) fix!: keep project name validation rules in sync between UI and CLI ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6470](https://github.com/vuejs/vue-cli/pull/6470) fix(SafariNomoduleFixPlugin): use RawSource instead of a plain object ([@KaelWD](https://github.com/KaelWD)) +* `@vue/cli-plugin-typescript` + * [#6456](https://github.com/vuejs/vue-cli/pull/6456) fix(typescript): add missing dependencies and `require.resolve` compiler ([@merceyz](https://github.com/merceyz)) +* `@vue/cli-ui` + * [#6443](https://github.com/vuejs/vue-cli/pull/6443) fix!: keep project name validation rules in sync between UI and CLI ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest` + * [#6454](https://github.com/vuejs/vue-cli/pull/6454) fix: fix jest migrator dependency merging ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-ui` + * [#6446](https://github.com/vuejs/vue-cli/pull/6446) ci: fix random failing ui tests ([@sodatea](https://github.com/sodatea)) + +#### Committers: 4 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Kael ([@KaelWD](https://github.com/KaelWD)) +- Kristoffer K. ([@merceyz](https://github.com/merceyz)) +- Thomas Timmers ([@TimmersThomas](https://github.com/TimmersThomas)) + + + +## 5.0.0-beta.0 (2021-04-25) + +#### :rocket: New Features +* `@vue/cli-plugin-typescript` + * [#6428](https://github.com/vuejs/vue-cli/pull/6428) feat(plugin-typescript): add all recommended tsconfig ([@IndexXuan](https://github.com/IndexXuan)) +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6420](https://github.com/vuejs/vue-cli/pull/6420) feat!: upgrade to css-minimizer-webpack-plugin v2 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6422](https://github.com/vuejs/vue-cli/pull/6422) feat!: always inject safari-nomodule-fix as an external script; drop `--no-unsafe-inline` flag ([@sodatea](https://github.com/sodatea)) + * [#6285](https://github.com/vuejs/vue-cli/pull/6285) feat(cli-service): provide jsconfig.json in no-ts template ([@yoyo930021](https://github.com/yoyo930021)) + * [#5997](https://github.com/vuejs/vue-cli/pull/5997) feat(cli-service): add inline loader support for html-webpack-plugin ([@ylc395](https://github.com/ylc395)) +* `@vue/babel-preset-app`, `@vue/cli-service` + * [#6419](https://github.com/vuejs/vue-cli/pull/6419) feat: only needs one bundle if all targets support es module ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-service`, `@vue/cli-ui` + * [#6416](https://github.com/vuejs/vue-cli/pull/6416) feat!: turn on modern mode by default, and provide a `--no-module` option ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#6405](https://github.com/vuejs/vue-cli/pull/6405) feat: support `vue.config.mjs` ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#6439](https://github.com/vuejs/vue-cli/pull/6439) feat!: drop IE11 support in CLI UI ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6420](https://github.com/vuejs/vue-cli/pull/6420) feat!: upgrade to css-minimizer-webpack-plugin v2 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6422](https://github.com/vuejs/vue-cli/pull/6422) feat!: always inject safari-nomodule-fix as an external script; drop `--no-unsafe-inline` flag ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-service`, `@vue/cli-ui` + * [#6416](https://github.com/vuejs/vue-cli/pull/6416) feat!: turn on modern mode by default, and provide a `--no-module` option ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-ui` + * [#6440](https://github.com/vuejs/vue-cli/pull/6440) fix(ui): fix publicPath documentation link ([@jeffreyyjp](https://github.com/jeffreyyjp)) +* `@vue/cli-service` + * [#6437](https://github.com/vuejs/vue-cli/pull/6437) fix: should not include IE11 target in Vue 3 projects ([@sodatea](https://github.com/sodatea)) + * [#6402](https://github.com/vuejs/vue-cli/pull/6402) fix(cli-service): respect the existing 'devtool' ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-unit-jest` + * [#6418](https://github.com/vuejs/vue-cli/pull/6418) Show fallback message for typescript jest preset if ts-jest is not in… ([@m0ksem](https://github.com/m0ksem)) +* `@vue/cli-plugin-unit-mocha` + * [#6400](https://github.com/vuejs/vue-cli/pull/6400) fix(mocha): workaround the SVGElement issue in Vue ([@fangbinwei](https://github.com/fangbinwei)) + +#### :memo: Documentation +* [#6438](https://github.com/vuejs/vue-cli/pull/6438) docs: add modern mode changes to the migration guide ([@sodatea](https://github.com/sodatea)) + +#### Committers: 8 +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- IU ([@yoyo930021](https://github.com/yoyo930021)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- Jeffrey Yang ([@jeffreyyjp](https://github.com/jeffreyyjp)) +- Maksim Nedoshev ([@m0ksem](https://github.com/m0ksem)) +- PENG Rui ([@IndexXuan](https://github.com/IndexXuan)) +- 叡山电车 ([@ylc395](https://github.com/ylc395)) + + + +## 5.0.0-alpha.8 (2021-03-24) + +#### :rocket: New Features +* `@vue/cli-plugin-babel`, `@vue/cli-service` + * [#6354](https://github.com/vuejs/vue-cli/pull/6354) feat: when `transpileDependencies` is set to `true`, transpile all dependencies in `node_modules` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6355](https://github.com/vuejs/vue-cli/pull/6355) feat: a `defineConfig` API from `@vue/cli-service` for better typing support in `vue.config.js` ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-service` + * [#6348](https://github.com/vuejs/vue-cli/pull/6348) chore!: bump copy-webpack-plugin to v8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest` + * [#6347](https://github.com/vuejs/vue-cli/pull/6347) refactor!: move vue-jest and ts-jest to peer dependencies ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6372](https://github.com/vuejs/vue-cli/pull/6372) fix: check hoisted postcss version ([@sodatea](https://github.com/sodatea)) + * [#6358](https://github.com/vuejs/vue-cli/pull/6358) fix: work around npm6/postcss8 hoisting issue ([@sodatea](https://github.com/sodatea)) + * [#6366](https://github.com/vuejs/vue-cli/pull/6366) fix(build): demo-lib.html compatible Vue 3 ([@jeneser](https://github.com/jeneser)) + +#### Committers: 4 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Péter Gaál ([@petergaal91](https://github.com/petergaal91)) +- Yazhe Wang ([@jeneser](https://github.com/jeneser)) +- zoomdong ([@fireairforce](https://github.com/fireairforce)) + + + +## 5.0.0-alpha.7 (2021-03-11) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6343](https://github.com/vuejs/vue-cli/pull/6343) fix: use cssnano v5.0.0-rc.1, work around a npm 6 hoisting bug ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 5.0.0-alpha.6 (2021-03-10) + +#### :rocket: New Features +* `@vue/cli-plugin-unit-jest` + * [#6335](https://github.com/vuejs/vue-cli/pull/6335) chore!: update vue-jest to v4.x ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6332](https://github.com/vuejs/vue-cli/pull/6332) feat!: upgrade to css-loader 5; remove `css.requireModuleExtension` & `css.modules` options ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-unit-jest` + * [#6335](https://github.com/vuejs/vue-cli/pull/6335) chore!: update vue-jest to v4.x ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6332](https://github.com/vuejs/vue-cli/pull/6332) feat!: upgrade to css-loader 5; remove `css.requireModuleExtension` & `css.modules` options ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#6314](https://github.com/vuejs/vue-cli/pull/6314) fix: fix `build --dest` option ([@sodatea](https://github.com/sodatea)) + +#### Committers: 2 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Tony Trinh ([@tony19](https://github.com/tony19)) + + + +## 5.0.0-alpha.5 (2021-02-23) + +#### :rocket: New Features +* `@vue/cli-plugin-webpack-4`, `@vue/cli` + * [#6307](https://github.com/vuejs/vue-cli/pull/6307) feat(GeneratorAPI): `forceOverwrite` option for `extendPackage` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6301](https://github.com/vuejs/vue-cli/pull/6301) feat!: use the latest versions of css preprocessor loaders by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript` + * [#6295](https://github.com/vuejs/vue-cli/pull/6295) feat!: update WebDriverIO to v7 ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6301](https://github.com/vuejs/vue-cli/pull/6301) feat!: use the latest versions of css preprocessor loaders by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript` + * [#6295](https://github.com/vuejs/vue-cli/pull/6295) feat!: update WebDriverIO to v7 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-ui`, `@vue/cli` + * [#6292](https://github.com/vuejs/vue-cli/pull/6292) chore!: drop Node.js v10 support ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript` + * [#6309](https://github.com/vuejs/vue-cli/pull/6309) fix(webdriverio): add `expect-webdriverio` to tsconfig ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-shared-utils` + * [#6254](https://github.com/vuejs/vue-cli/pull/6254) fix: correctly pad log strings ([@xiek881028](https://github.com/xiek881028)) +* `@vue/cli` + * [#6304](https://github.com/vuejs/vue-cli/pull/6304) fix(generator): support npm package aliases ("@npm:" in version specifier) ([@nuochong](https://github.com/nuochong)) + * [#6303](https://github.com/vuejs/vue-cli/pull/6303) fix(create): write the lint-staged config to its own file (Closes [#6298](https://github.com/vuejs/vue-cli/issues/6298)) ([@HexPandaa](https://github.com/HexPandaa)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex`, `@vue/cli-plugin-webpack-4`, `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli-test-utils`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui`, `@vue/cli` + * [#6291](https://github.com/vuejs/vue-cli/pull/6291) fix: better dev server & webpack 4 compatibility and some trivial dependency updates ([@sodatea](https://github.com/sodatea)) + +#### Committers: 4 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Superman ([@nuochong](https://github.com/nuochong)) +- [@HexPandaa](https://github.com/HexPandaa) +- xiek ([@xiek881028](https://github.com/xiek881028)) + + + +## 5.0.0-alpha.4 (2021-02-18) + +#### :rocket: New Features +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6279](https://github.com/vuejs/vue-cli/pull/6279) feat!: update copy & terser plugin, move more legacy code to webpack-4 plugin ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6269](https://github.com/vuejs/vue-cli/pull/6269) feat: use html-webpack-plugin v5 by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript` + * [#6235](https://github.com/vuejs/vue-cli/pull/6235) feat(typescript): add `useDefineForClassFields` option in tsconfig template ([@ktsn](https://github.com/ktsn)) + +#### :boom: Breaking Changes +* `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6279](https://github.com/vuejs/vue-cli/pull/6279) feat!: update copy & terser plugin, move more legacy code to webpack-4 plugin ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-webpack-4`, `@vue/cli-service` + * [#6269](https://github.com/vuejs/vue-cli/pull/6269) feat: use html-webpack-plugin v5 by default ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-pwa` + * [#6277](https://github.com/vuejs/vue-cli/pull/6277) fix(cli-plugin-pwa): webpack5 warning for emitting manifest.json ([@awill1988](https://github.com/awill1988)) +* `@vue/cli-service` + * [#6230](https://github.com/vuejs/vue-cli/pull/6230) fix: mini-css-extract-plugin publicPath option can be an absolute path ([@Veath](https://github.com/Veath)) + * [#6221](https://github.com/vuejs/vue-cli/pull/6221) fix(cli-service): avoiding recreating dist directory ([@fangbinwei](https://github.com/fangbinwei)) + +#### :house: Internal +* `@vue/cli` + * [#6242](https://github.com/vuejs/vue-cli/pull/6242) chore: upgrade commander to v7 ([@sodatea](https://github.com/sodatea)) + +#### Committers: 7 +- Adam Williams ([@awill1988](https://github.com/awill1988)) +- Andy Chen ([@tjcchen](https://github.com/tjcchen)) +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Katashin ([@ktsn](https://github.com/ktsn)) +- Robin Hellemans ([@Robin-Hoodie](https://github.com/Robin-Hoodie)) +- [@Veath](https://github.com/Veath) + + + +## 5.0.0-alpha.3 (2021-01-22) + +#### :rocket: New Features +* `@vue/cli-plugin-pwa` + * [#6198](https://github.com/vuejs/vue-cli/pull/6198) Support svg favicon ([@mauriciabad](https://github.com/mauriciabad)) +* `@vue/cli-service` + * [#6187](https://github.com/vuejs/vue-cli/pull/6187) feat!: bump default sass-loader version to v10, drop sass-loader v7 support ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui`, `@vue/cli` + * [#6001](https://github.com/vuejs/vue-cli/pull/6001) feat: open browser when toast clicked ([@tony19](https://github.com/tony19)) + +#### :boom: Breaking Changes +* `@vue/cli-service` + * [#6187](https://github.com/vuejs/vue-cli/pull/6187) feat!: bump default sass-loader version to v10, drop sass-loader v7 support ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service`, `@vue/cli-shared-utils` + * [#5794](https://github.com/vuejs/vue-cli/pull/5794) fix(cli): resolve plugins relative to the package context ([@merceyz](https://github.com/merceyz)) +* `@vue/cli` + * [#6224](https://github.com/vuejs/vue-cli/pull/6224) fix: discard `NODE_ENV` when installing project dependencies ([@sodatea](https://github.com/sodatea)) + * [#6207](https://github.com/vuejs/vue-cli/pull/6207) fix: support basic auth for npm registry access ([@bodograumann](https://github.com/bodograumann)) +* `@vue/cli-service` + * [#6218](https://github.com/vuejs/vue-cli/pull/6218) fix: "commonjs2" target should not be used with "output.library" ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#6215](https://github.com/vuejs/vue-cli/pull/6215) fix(unit-mocha): shouldn't require webpack-4 plugin with cli-service v4 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui` + * [#6192](https://github.com/vuejs/vue-cli/pull/6192) fix: should use graphql v15 at all levels of dependency ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-plugin-babel` + * [#6222](https://github.com/vuejs/vue-cli/pull/6222) chore: disable cacheCompression for babel-loader by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui` + * [#6189](https://github.com/vuejs/vue-cli/pull/6189) refactor: fix eslint warnings in the cli-ui codebase ([@sodatea](https://github.com/sodatea)) + +#### Committers: 5 +- Bodo Graumann ([@bodograumann](https://github.com/bodograumann)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Kristoffer K. ([@merceyz](https://github.com/merceyz)) +- Maurici Abad Gutierrez ([@mauriciabad](https://github.com/mauriciabad)) +- Tony Trinh ([@tony19](https://github.com/tony19)) + + + +## 5.0.0-alpha.2 (2021-01-06) + +#### :rocket: New Features +* `@vue/cli` + * [#5537](https://github.com/vuejs/vue-cli/pull/5537) feat(cli): make globby includes dot files ([@fxxjdedd](https://github.com/fxxjdedd)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-pwa` + * [#5327](https://github.com/vuejs/vue-cli/pull/5327) fix pwa installability when using noopServiceWorker "Page does not work offline" ([@kubenstein](https://github.com/kubenstein)) +* `@vue/cli-plugin-unit-mocha` + * [#6186](https://github.com/vuejs/vue-cli/pull/6186) fix(mocha): workaround the ShadowRoot issue in Vue 3.0.5 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6162](https://github.com/vuejs/vue-cli/pull/6162) fix(cli-service): restrict request headers of historyApiFallback in WebpackDevServer ([@githoniel](https://github.com/githoniel)) +* `@vue/cli-plugin-unit-jest` + * [#6170](https://github.com/vuejs/vue-cli/pull/6170) fix: add missing jest-transform-stub media types (#6169) ([@raineorshine](https://github.com/raineorshine)) +* `@vue/cli` + * [#6011](https://github.com/vuejs/vue-cli/pull/6011) fix(generator): avoid doing redundant write operations ([@fangbinwei](https://github.com/fangbinwei)) + +#### :memo: Documentation +* [#6176](https://github.com/vuejs/vue-cli/pull/6176) Fixed some typos on deployment.md ([@black-fyre](https://github.com/black-fyre)) +* [#5927](https://github.com/vuejs/vue-cli/pull/5927) Update skip plugins section of cli-service ([@markjszy](https://github.com/markjszy)) +* [#6093](https://github.com/vuejs/vue-cli/pull/6093) Easier Netlify setup ([@mauriciabad](https://github.com/mauriciabad)) +* [#6050](https://github.com/vuejs/vue-cli/pull/6050) mode-and-env doc need be updated ([@theniceangel](https://github.com/theniceangel)) +* [#6050](https://github.com/vuejs/vue-cli/pull/6050) mode-and-env doc need be updated ([@theniceangel](https://github.com/theniceangel)) + +#### :house: Internal +* `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli-ui`, `@vue/cli` + * [#6152](https://github.com/vuejs/vue-cli/pull/6152) chore: some trivial dependency version bumps ([@sodatea](https://github.com/sodatea)) + +#### Committers: 11 +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Dahunsi Fehintoluwa ([@black-fyre](https://github.com/black-fyre)) +- Githoniel ([@githoniel](https://github.com/githoniel)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Jakub Niewczas ([@kubenstein](https://github.com/kubenstein)) +- JiZhi ([@theniceangel](https://github.com/theniceangel)) +- Mark Szymanski ([@markjszy](https://github.com/markjszy)) +- Maurici Abad Gutierrez ([@mauriciabad](https://github.com/mauriciabad)) +- Raine Revere ([@raineorshine](https://github.com/raineorshine)) +- fxxjdedd ([@fxxjdedd](https://github.com/fxxjdedd)) + + + +## 5.0.0-alpha.1 (2021-01-06) + +#### :memo: Documentation +* [#6128](https://github.com/vuejs/vue-cli/pull/6128) docs: don't add `.loader()` when modifying vue-loader options ([@sodatea](https://github.com/sodatea)) +* [#6005](https://github.com/vuejs/vue-cli/pull/6005) docs: [RU] Translation update ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) + +#### Committers: 2 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 5.0.0-alpha.0 (2020-12-14) + +#### :rocket: New Features +* `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-webpack-4`, `@vue/cli-shared-utils` + * [#6144](https://github.com/vuejs/vue-cli/pull/6144) feat: add a @vue/cli-plugin-webpack-4 package for future use ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex` + * [#6132](https://github.com/vuejs/vue-cli/pull/6132) chore!: prepare for v5 peer dependencies, drop v4 prereleases ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint`, `@vue/cli-service`, `@vue/cli-ui` + * [#6136](https://github.com/vuejs/vue-cli/pull/6136) feat: bump lint-staged to v10 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6130](https://github.com/vuejs/vue-cli/pull/6130) chore!: bump stylus-loader from v3 to v4 ([@jeneser](https://github.com/jeneser)) +* `@vue/cli-plugin-eslint`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#6123](https://github.com/vuejs/vue-cli/pull/6123) feat: update eslint-related packages ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-ui` + * [#6129](https://github.com/vuejs/vue-cli/pull/6129) chore!: update typescript-related dependencies ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha` + * [#6121](https://github.com/vuejs/vue-cli/pull/6121) feat!: update mocha to v8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#6120](https://github.com/vuejs/vue-cli/pull/6120) feat: update cypress to v6 ([@sodatea](https://github.com/sodatea)) + * [#6062](https://github.com/vuejs/vue-cli/pull/6062) fix(cypress): allow users to update cypress ([@elevatebart](https://github.com/elevatebart)) +* `@vue/cli-service`, `@vue/cli-ui` + * [#6108](https://github.com/vuejs/vue-cli/pull/6108) feat!: upgrade postcss-loader, using postcss 8 by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service-global`, `@vue/cli` + * [#6115](https://github.com/vuejs/vue-cli/pull/6115) feat!: make `vue serve/build` aliases to `npm run serve/build` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest` + * [#6116](https://github.com/vuejs/vue-cli/pull/6116) feat!: update jest to v26 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-service-global` + * [#6094](https://github.com/vuejs/vue-cli/pull/6094) feat: replace eslint-loader by eslint-webpack-plugin ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli-ui` + * [#6060](https://github.com/vuejs/vue-cli/pull/6060) feat!: support and use webpack 5 as default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint`, `@vue/cli-test-utils`, `@vue/cli-ui`, `@vue/cli` + * [#6059](https://github.com/vuejs/vue-cli/pull/6059) feat(eslint): support eslint7 and @babel/eslint-parser ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-eslint` + * [#4850](https://github.com/vuejs/vue-cli/pull/4850) feat(lint): add output file option (Closes [#4849](https://github.com/vuejs/vue-cli/issues/4849)) ([@ataylorme](https://github.com/ataylorme)) + +#### :boom: Breaking Changes +* `@vue/cli-service`, `@vue/cli-ui` + * [#6140](https://github.com/vuejs/vue-cli/pull/6140) refactor!: replace optimize-cssnano-plugin with css-minimizer-webpack-plugin ([@sodatea](https://github.com/sodatea)) + * [#6108](https://github.com/vuejs/vue-cli/pull/6108) feat!: upgrade postcss-loader, using postcss 8 by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex` + * [#6132](https://github.com/vuejs/vue-cli/pull/6132) chore!: prepare for v5 peer dependencies, drop v4 prereleases ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#6133](https://github.com/vuejs/vue-cli/pull/6133) chore!: bump ejs to v3 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6130](https://github.com/vuejs/vue-cli/pull/6130) chore!: bump stylus-loader from v3 to v4 ([@jeneser](https://github.com/jeneser)) + * [#5951](https://github.com/vuejs/vue-cli/pull/5951) chore!: some trivial dependency major version updates ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-ui` + * [#6129](https://github.com/vuejs/vue-cli/pull/6129) chore!: update typescript-related dependencies ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha` + * [#6121](https://github.com/vuejs/vue-cli/pull/6121) feat!: update mocha to v8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service-global`, `@vue/cli` + * [#6115](https://github.com/vuejs/vue-cli/pull/6115) feat!: make `vue serve/build` aliases to `npm run serve/build` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest` + * [#6116](https://github.com/vuejs/vue-cli/pull/6116) feat!: update jest to v26 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-service-global` + * [#6094](https://github.com/vuejs/vue-cli/pull/6094) feat: replace eslint-loader by eslint-webpack-plugin ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli-ui` + * [#6060](https://github.com/vuejs/vue-cli/pull/6060) feat!: support and use webpack 5 as default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli` + * [#6090](https://github.com/vuejs/vue-cli/pull/6090) chore: remove deprecated node-sass ([@andreiTn](https://github.com/andreiTn)) + * [#6051](https://github.com/vuejs/vue-cli/pull/6051) chore!: drop support of NPM 5 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli-ui`, `@vue/cli` + * [#5973](https://github.com/vuejs/vue-cli/pull/5973) chore!: bump joi to v17 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-ui`, `@vue/cli` + * [#6052](https://github.com/vuejs/vue-cli/pull/6052) chore!: drop support of end-of-life node releases (8, 11, 13) ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli` + * [#6009](https://github.com/vuejs/vue-cli/pull/6009) refactor!: replace request with node-fetch ([@jeneser](https://github.com/jeneser)) +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#5951](https://github.com/vuejs/vue-cli/pull/5951) chore!: some trivial dependency major version updates ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript` + * [#5941](https://github.com/vuejs/vue-cli/pull/5941) feat!: bump fork-ts-checker-webpack-plugin version to v5 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha` + * [#5907](https://github.com/vuejs/vue-cli/pull/5907) chore!: bump unit-mocha dependency versions ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#5907](https://github.com/vuejs/vue-cli/pull/5907) chore!: bump unit-mocha dependency versions ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint`, `@vue/cli-service-global` + * [#5870](https://github.com/vuejs/vue-cli/pull/5870) chore!: update eslint-loader, minimum supported ESLint version is 6 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript`, `@vue/cli` + * [#5065](https://github.com/vuejs/vue-cli/pull/5065) Remove linter option TSLint ([@Shinigami92](https://github.com/Shinigami92)) + +#### :bug: Bug Fix +* `@vue/cli` + * [#6145](https://github.com/vuejs/vue-cli/pull/6145) fix: fix cypress mirror url for cypress version > 3 ([@sodatea](https://github.com/sodatea)) + * [#6137](https://github.com/vuejs/vue-cli/pull/6137) fix: fix usage of cmd-shim ([@fangbinwei](https://github.com/fangbinwei)) + * [#5921](https://github.com/vuejs/vue-cli/pull/5921) fix(cli): only process template file contents, bump yaml-front-matter… ([@ferm10n](https://github.com/ferm10n)) + * [#5961](https://github.com/vuejs/vue-cli/pull/5961) fix: npm 7 compat by turning on `legacy-peer-deps` flag ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6101](https://github.com/vuejs/vue-cli/pull/6101) fix(cli-service): don't write entry-wc to node_modules ([@merceyz](https://github.com/merceyz)) + * [#6066](https://github.com/vuejs/vue-cli/pull/6066) fix(cli-service): pass --public host to devserver ([@jonaskuske](https://github.com/jonaskuske)) +* `@vue/cli-plugin-unit-mocha`, `@vue/cli-service` + * [#6097](https://github.com/vuejs/vue-cli/pull/6097) fix(mocha): disable SSR optimization for Vue 3 testing ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint` + * [#6020](https://github.com/vuejs/vue-cli/pull/6020) fix(generator): upgrade to prettier v2 ([@jeneser](https://github.com/jeneser)) +* `@vue/cli-ui` + * [#6000](https://github.com/vuejs/vue-cli/pull/6000) fix: prevent snoretoast shortcut, set notif title (#2720) ([@tony19](https://github.com/tony19)) +* `@vue/cli-service-global`, `@vue/cli-service` + * [#5992](https://github.com/vuejs/vue-cli/pull/5992) fix: using `lang` attribute with empty string in html template ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-typescript` + * [#5975](https://github.com/vuejs/vue-cli/pull/5975) fix: update vue-shims for Vue v3.0.1 ([@cexbrayat](https://github.com/cexbrayat)) + +#### :house: Internal +* `@vue/cli-plugin-babel`, `@vue/cli-service` + * [#6142](https://github.com/vuejs/vue-cli/pull/6142) refactor: replace cache-loader with babel-loader's built-in cache ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-ui` + * [#6140](https://github.com/vuejs/vue-cli/pull/6140) refactor!: replace optimize-cssnano-plugin with css-minimizer-webpack-plugin ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#6127](https://github.com/vuejs/vue-cli/pull/6127) chore: update cmd-shim and move it to devDependencies ([@sodatea](https://github.com/sodatea)) + * [#6102](https://github.com/vuejs/vue-cli/pull/6102) perf(packages/@vue/cli/bin/vue.js): deleting the EOL_NODE_MAJORS chec… ([@ChanningHan](https://github.com/ChanningHan)) +* `@vue/cli-service-global`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#6078](https://github.com/vuejs/vue-cli/pull/6078) refactor: sub-package eslint maintance ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli-ui`, `@vue/cli` + * [#5973](https://github.com/vuejs/vue-cli/pull/5973) chore!: bump joi to v17 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript` + * [#6053](https://github.com/vuejs/vue-cli/pull/6053) fix(cli-plugin-typescript): remove getPrompts function in prompts.js ([@jeneser](https://github.com/jeneser)) +* `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli` + * [#6009](https://github.com/vuejs/vue-cli/pull/6009) refactor!: replace request with node-fetch ([@jeneser](https://github.com/jeneser)) + +#### :hammer: Underlying Tools +* `@vue/cli` + * [#6133](https://github.com/vuejs/vue-cli/pull/6133) chore!: bump ejs to v3 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#6092](https://github.com/vuejs/vue-cli/pull/6092) chore: webpack-bundle-analyzer to ^4.1.0 ([@genie-youn](https://github.com/genie-youn)) +* `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-mocha` + * [#5907](https://github.com/vuejs/vue-cli/pull/5907) chore!: bump unit-mocha dependency versions ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#5907](https://github.com/vuejs/vue-cli/pull/5907) chore!: bump unit-mocha dependency versions ([@sodatea](https://github.com/sodatea)) + +#### Committers: 19 +- Andrei ([@andreiTn](https://github.com/andreiTn)) +- Andrew Taylor ([@ataylorme](https://github.com/ataylorme)) +- Barthélémy Ledoux ([@elevatebart](https://github.com/elevatebart)) +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Channing ([@ChanningHan](https://github.com/ChanningHan)) +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Githoniel ([@githoniel](https://github.com/githoniel)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- JayZhong ([@zzzJH](https://github.com/zzzJH)) +- Jisoo Youn ([@genie-youn](https://github.com/genie-youn)) +- John Sanders ([@ferm10n](https://github.com/ferm10n)) +- Jonas ([@jonaskuske](https://github.com/jonaskuske)) +- Kristoffer K. ([@merceyz](https://github.com/merceyz)) +- Max Coplan ([@vegerot](https://github.com/vegerot)) +- Parker Mauney ([@ParkerM](https://github.com/ParkerM)) +- Shinigami ([@Shinigami92](https://github.com/Shinigami92)) +- Tony Trinh ([@tony19](https://github.com/tony19)) +- Yazhe Wang ([@jeneser](https://github.com/jeneser)) + + +## 4.5.19 (2022-06-28) + +IMPORTANT NOTE: [IE 11 has reached End-of-Life](https://docs.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge#what-is-the-lifecycle-policy-for-internet-explorer-). The default `browserslist` query no longer includes IE 11 as a target. +If your project still has to support IE 11, you **MUST** manually add `IE 11` to the last line of the `.browserslistrc` file in the project (or `browserslist` field in `package.json`) + +#### :bug: Bug Fix + +* `@vue/babel-preset-app` + * [[c7fa1cf](https://github.com/vuejs/vue-cli/commit/c7fa1cf)] fix: always transpile syntaxes introduced in ES2020 or later, so that optional chaining and nullish coalescing syntaxes won't cause errors in webpack 4 and ESLint 6. +* `@vue/cli-plugin-typescript` + * [[5b57792](https://github.com/vuejs/vue-cli/commit/5b57792)] fix: typechecking with Vue 2.7, fixes #7213 + + +## 4.5.18 (2022-06-16) + +Fix compatibility with the upcoming Vue 2.7 (currently in alpha) and Vue Loader 15.10 (currently in beta). + +In Vue 2.7, `vue-template-compiler` is no longer a required peer dependency. Rather, there's a new export under the main package as `vue/compiler-sfc`. + + +## 4.5.17 (2022-03-23) + +#### :bug: Bug Fix +* `@vue/cli-shared-utils`, `@vue/cli-ui` + * [d7a9881](https://github.com/vuejs/vue-cli/commit/d7a9881) fix: replace `node-ipc` with `@achrinza/node-ipc` to further secure the dependency chain + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + +## 4.5.16 (2022-03-15) + +#### :bug: Bug Fix +* `@vue/cli-service` + * Fix demo-lib.html and demo-wc.html for Vue 2 +* `@vue/cli-shared-utils`, `@vue/cli-ui` + * Lock `node-ipc` to v9.2.1 + + +## 4.5.15 (2021-10-28) + +#### Bug Fixes + +* fix: set `.mjs` file type to `javascript/auto` [[15b1e1b]](https://github.com/vuejs/vue-cli/commit/15b1e1b6bfa40fe0b69db304a2439c66ff9ba65f) + +This change allows an `.mjs` file to import named exports from `.cjs` and plain `.js` files. +Fixes compatibility with `pinia`. + + +## 4.5.14 (2021-10-14) + +#### Security Fixes + +This version fixed a CORS vulnerability and an XSS vulnerability in Vue CLI UI. +We recommend all users of `vue ui` to upgrade to this version as soon as possible. + +#### Credits: +Ngo Wei Lin ([@Creastery](https://twitter.com/creastery)) of STAR Labs ([@starlabs_sg](https://twitter.com/starlabs_sg)) + + +## 4.5.13 (2021-05-08) + +#### :bug: Bug Fix +* `@vue/babel-preset-app` + * [#6459](https://github.com/vuejs/vue-cli/pull/6459) fix: fix modern mode optional chaining syntax tranpilation ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#6400](https://github.com/vuejs/vue-cli/pull/6400) fix(mocha): workaround the SVGElement issue in Vue 3 ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-service` + * [#6455](https://github.com/vuejs/vue-cli/pull/6455) fix: get rid of ssri vulnerability warnings ([@sodatea](https://github.com/sodatea)) + +### Others + +* [#6300](https://github.com/vuejs/vue-cli/pull/6300) chore: remove the word "Preview" from vue 3 preset ([@sodatea](https://github.com/sodatea)) + +#### Committers: 3 +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Tony Trinh ([@tony19](https://github.com/tony19)) + + + +## 4.5.12 (2021-03-17) + +* bump `vue-codemod` to work around an NPM hoisting bug +* bump minimum required JSX preset / plugin versions, fixes https://github.com/vuejs/jsx/issues/183 +* bump default `typescript` version to 4.1 and `prettier` version to 2.x for new projects, fixes [#6299](https://github.com/vuejs/vue-cli/pull/6299) + + + +## 4.5.11 (2021-01-22) + +#### :bug: Bug Fix +* `@vue/cli` + * [#6207](https://github.com/vuejs/vue-cli/pull/6207) fix: support basic auth for npm registry access ([@bodograumann](https://github.com/bodograumann)) + +#### Committers: 1 +- Bodo Graumann ([@bodograumann](https://github.com/bodograumann)) + + + +## 4.5.10 (2021-01-06) + +#### :bug: Bug Fix +* `@vue/cli-plugin-unit-mocha` + * [#6186](https://github.com/vuejs/vue-cli/pull/6186) fix(mocha): workaround the ShadowRoot issue in Vue 3.0.5 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha`, `@vue/cli-service` + * [#6097](https://github.com/vuejs/vue-cli/pull/6097) fix(mocha): disable SSR optimization for Vue 3 testing ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui` + * [#6000](https://github.com/vuejs/vue-cli/pull/6000) fix: prevent snoretoast shortcut, set notif title (#2720) ([@tony19](https://github.com/tony19)) +* `@vue/cli-service-global`, `@vue/cli-service` + * [#5992](https://github.com/vuejs/vue-cli/pull/5992) fix: using `lang` attribute with empty string in html template ([@fangbinwei](https://github.com/fangbinwei)) + +#### Committers: 3 +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Tony Trinh ([@tony19](https://github.com/tony19)) + + + +## 4.5.9 (2020-11-17) + +#### :rocket: New Features +* `@vue/cli-plugin-e2e-cypress` + * [#6062](https://github.com/vuejs/vue-cli/pull/6062) fix(cypress): allow users to update cypress ([@elevatebart](https://github.com/elevatebart)) + +#### Committers: 1 +- Barthélémy Ledoux ([@elevatebart](https://github.com/elevatebart)) + + + +## 4.5.8 (2020-10-19) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#5975](https://github.com/vuejs/vue-cli/pull/5975) fix: update vue-shims for Vue v3.0.1 ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli` + * [#5961](https://github.com/vuejs/vue-cli/pull/5961) fix: npm 7 compat by turning on `legacy-peer-deps` flag ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint` + * [#5962](https://github.com/vuejs/vue-cli/pull/5962) fix: narrow the eslint peer dep version range, avoiding npm 7 error ([@sodatea](https://github.com/sodatea)) + +#### Committers: 2 +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.5.7 (2020-10-07) + +#### :bug: Bug Fix +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#5903](https://github.com/vuejs/vue-cli/pull/5903) fix: update the `.vue` file shim for Vue 3 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5871](https://github.com/vuejs/vue-cli/pull/5871) fix: more accurate warning message for missing global peer dependencies ([@sodatea](https://github.com/sodatea)) + * [#5902](https://github.com/vuejs/vue-cli/pull/5902) fix: incorrectly read Taobao binary mirror configuration. ([@godky](https://github.com/godky)) + * [#5892](https://github.com/vuejs/vue-cli/pull/5892) fix: respect scope when resolving package metadata ([@bodograumann](https://github.com/bodograumann)) +* `@vue/cli-plugin-pwa`, `@vue/cli-service` + * [#5899](https://github.com/vuejs/vue-cli/pull/5899) fix: shouldn't remove attribute quotes in HTML ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5835](https://github.com/vuejs/vue-cli/pull/5835) Update Vercel deployment instructions ([@timothyis](https://github.com/timothyis)) + +#### Committers: 4 +- Bodo Graumann ([@bodograumann](https://github.com/bodograumann)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Timothy ([@timothyis](https://github.com/timothyis)) +- kzhang ([@godky](https://github.com/godky)) + + + +## 4.5.6 (2020-09-10) + +#### :bug: Bug Fix +* `@vue/cli` + * [#5869](https://github.com/vuejs/vue-cli/pull/5869) fix: skip checking git gpgSign config ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.5.5 (2020-09-10) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#5868](https://github.com/vuejs/vue-cli/pull/5868) fix: enable some syntax extensions by default for vue script compiler ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-router`, `@vue/cli-service` + * [#5852](https://github.com/vuejs/vue-cli/pull/5852) fix: fix duplicate id="app" in Vue 3 project template ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha` + * [#5591](https://github.com/vuejs/vue-cli/pull/5591) fix(unit-jest, unit-mocha): generate passing tests when `bare` option is used with router enabled (#3544) ([@IwalkAlone](https://github.com/IwalkAlone)) +* `@vue/cli-plugin-pwa` + * [#5820](https://github.com/vuejs/vue-cli/pull/5820) fix: allow turning off theme color tags ([@GabrielGMartinsBr](https://github.com/GabrielGMartinsBr)) +* `@vue/cli` + * [#5827](https://github.com/vuejs/vue-cli/pull/5827) fix: fix support for Node.js v8 and deprecate it ([@sodatea](https://github.com/sodatea)) + * [#5823](https://github.com/vuejs/vue-cli/pull/5823) Handle GPG sign git config for initial commit ([@spenserblack](https://github.com/spenserblack)) + * [#5808](https://github.com/vuejs/vue-cli/pull/5808) fix: strip non-ansi characters from registry config ([@sodatea](https://github.com/sodatea)) + * [#5801](https://github.com/vuejs/vue-cli/pull/5801) fix: do not throw when api.render is called from an anonymous function ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-ui` + * [#3687](https://github.com/vuejs/vue-cli/pull/3687) perf(ui): improve get folder list to use Promises instead of sync ([@pikax](https://github.com/pikax)) + +#### :hammer: Underlying Tools +* `@vue/babel-preset-app` + * [#5831](https://github.com/vuejs/vue-cli/pull/5831) chore: rename jsx package scope from ant-design-vue to vue ([@Amour1688](https://github.com/Amour1688)) + +#### Committers: 8 +- Booker Zhao ([@binggg](https://github.com/binggg)) +- Carlos Rodrigues ([@pikax](https://github.com/pikax)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Renan Cidale Assumpcao ([@rcidaleassumpo](https://github.com/rcidaleassumpo)) +- Sergey Skrynnikov ([@IwalkAlone](https://github.com/IwalkAlone)) +- Spenser Black ([@spenserblack](https://github.com/spenserblack)) +- [@GabrielGMartinsBr](https://github.com/GabrielGMartinsBr) +- 天泽 ([@Amour1688](https://github.com/Amour1688)) + + + +## 4.5.4 (2020-08-18) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#5798](https://github.com/vuejs/vue-cli/pull/5798) fix: fix Vue 3 + TS + Router template ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5788](https://github.com/vuejs/vue-cli/pull/5788) fix: ensure Dev Tool is enabled in Vue 3 runtime ([@sodatea](https://github.com/sodatea)) + * [#5693](https://github.com/vuejs/vue-cli/pull/5693) fix: mayProxy.isPublicFileRequest judgment ([@Blacate](https://github.com/Blacate)) +* `@vue/cli` + * [#5778](https://github.com/vuejs/vue-cli/pull/5778) fix: missing proxy argument ([@RobbinBaauw](https://github.com/RobbinBaauw)) + +#### Committers: 3 +- Blacate ([@Blacate](https://github.com/Blacate)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Robbin Baauw ([@RobbinBaauw](https://github.com/RobbinBaauw)) + + + +## 4.5.3 (2020-08-11) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#5774](https://github.com/vuejs/vue-cli/pull/5774) fix: load vue from `@vue/cli-service-global` on `vue serve`/`vue build` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript` + * [#5769](https://github.com/vuejs/vue-cli/pull/5769) fix: add missing mocha type if wdio is not installed along with any unit testing frameworks ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript` + * [#5771](https://github.com/vuejs/vue-cli/pull/5771) fix: only replace App.vue when there's no router plugin ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.5.2 (2020-08-10) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#5768](https://github.com/vuejs/vue-cli/pull/5768) fix: no longer need a shim for fork-ts-checker vue 3 support ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* `@vue/babel-preset-app`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli-service`, `@vue/cli` + * [#5694](https://github.com/vuejs/vue-cli/pull/5694) [Fix] common misspelling errors ([@Necmttn](https://github.com/Necmttn)) + +#### :house: Internal +* `@vue/babel-preset-app`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli-service`, `@vue/cli` + * [#5694](https://github.com/vuejs/vue-cli/pull/5694) [Fix] common misspelling errors ([@Necmttn](https://github.com/Necmttn)) + +#### Committers: 3 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Necmettin Karakaya ([@Necmttn](https://github.com/Necmttn)) + + + +## 4.5.1 (2020-08-06) + +#### :rocket: New Features +* `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-shared-utils`, `@vue/cli` + * [#5479](https://github.com/vuejs/vue-cli/pull/5479) feat(e2e-webdriverio): add e2e plugin for WebdriverIO ([@christian-bromann](https://github.com/christian-bromann)) +* `@vue/cli-service` + * [#5725](https://github.com/vuejs/vue-cli/pull/5725) feat: implement a migrator that removes `vue-cli-plugin-next` as it's no longer needed ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#5731](https://github.com/vuejs/vue-cli/pull/5731) fix: fix skipLibCheck default value for `vue create` ([@sodatea](https://github.com/sodatea)) + * [#5722](https://github.com/vuejs/vue-cli/pull/5722) fix: use fork-ts-checker-webpack-plugin v5 for vue 3 type checking ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5744](https://github.com/vuejs/vue-cli/pull/5744) fix: ignore `.svn/**` when reading and writing files ([@sodatea](https://github.com/sodatea)) + * [#5736](https://github.com/vuejs/vue-cli/pull/5736) fix(e2e): shouldn't install webdrivers for unchecked browsers on creation ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5718](https://github.com/vuejs/vue-cli/pull/5718) fix: make vue-loader-v16 an optional dependency, avoid crashing npm 5 ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-service` + * [#5759](https://github.com/vuejs/vue-cli/pull/5759) chore: update type definition test ([@jamesgeorge007](https://github.com/jamesgeorge007)) + * [#5735](https://github.com/vuejs/vue-cli/pull/5735) refactor(cli-service): webpack `devtool` option ([@jeneser](https://github.com/jeneser)) + +#### :hammer: Underlying Tools +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-e2e-webdriverio`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-service`, `@vue/cli-test-utils` + * [#5742](https://github.com/vuejs/vue-cli/pull/5742) chore: dependency maintenance ([@sodatea](https://github.com/sodatea)) + +#### Committers: 6 +- Booker Zhao ([@binggg](https://github.com/binggg)) +- Christian Bromann ([@christian-bromann](https://github.com/christian-bromann)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- Renato Vicente ([@Renato66](https://github.com/Renato66)) +- Yazhe Wang ([@jeneser](https://github.com/jeneser)) + + + +## 4.5.0 (2020-07-24) + +#### :rocket: New Features +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex`, `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli-ui`, `@vue/cli` + * [#5637](https://github.com/vuejs/vue-cli/pull/5637) feat: allow choosing vue version on creation (and in presets) ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript` + * [#5688](https://github.com/vuejs/vue-cli/pull/5688) feat: add `skipLibCheck` option in the TS template (defaults to `true`) ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli` + * [#5356](https://github.com/vuejs/vue-cli/pull/5356) feat(cli,cli-service,cli-test-utils): add ts declaration ([@fangbinwei](https://github.com/fangbinwei)) +* `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#5570](https://github.com/vuejs/vue-cli/pull/5570) feat: detect and compile Vue 3 projects ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli` + * [#5556](https://github.com/vuejs/vue-cli/pull/5556) feat: support node nightly builds ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#5681](https://github.com/vuejs/vue-cli/pull/5681) Fix Kubernetes container detection ([@lbogdan](https://github.com/lbogdan)) +* `@vue/babel-preset-app` + * [#5543](https://github.com/vuejs/vue-cli/pull/5543) fix: better error message for non-existent polyfill names ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5671](https://github.com/vuejs/vue-cli/pull/5671) docs(zh): change line to lines in plugin-dev.md ([@zhouxinyong](https://github.com/zhouxinyong)) +* [#5668](https://github.com/vuejs/vue-cli/pull/5668) docs(zh): `additionalData` example for sass-loader 9.0 ([@chuzhixin](https://github.com/chuzhixin)) +* [#5408](https://github.com/vuejs/vue-cli/pull/5408) docs: explain pwa head/manifest icons ([@DRBragg](https://github.com/DRBragg)) + +#### :house: Internal +* `@vue/cli-shared-utils` + * [#5700](https://github.com/vuejs/vue-cli/pull/5700) refactor: use console.clear to clear the log ([@imtaotao](https://github.com/imtaotao)) +* `@vue/cli-service`, `@vue/cli` + * [#5629](https://github.com/vuejs/vue-cli/pull/5629) refactor: replace jscodeshift with vue-codemod ([@sodatea](https://github.com/sodatea)) + +#### Committers: 7 +- Arthur ([@imtaotao](https://github.com/imtaotao)) +- Binwei Fang ([@fangbinwei](https://github.com/fangbinwei)) +- Bogdan Luca ([@lbogdan](https://github.com/lbogdan)) +- Drew Bragg ([@DRBragg](https://github.com/DRBragg)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- good luck ([@chuzhixin](https://github.com/chuzhixin)) +- vimvinter ([@zhouxinyong](https://github.com/zhouxinyong)) + + + +## 4.4.6 (2020-06-24) + +#### :bug: Bug Fix +* `@vue/cli` + * [#5614](https://github.com/vuejs/vue-cli/pull/5614) fix jscodeshift peer dependency error ([@sodatea](https://github.com/sodatea)) + * [#5609](https://github.com/vuejs/vue-cli/pull/5609) fix: fix support for some legacy registry servers ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5603](https://github.com/vuejs/vue-cli/pull/5603) docs: @babel-preset/env -> @babel/preset-env ([@sodatea](https://github.com/sodatea)) +* [#5603](https://github.com/vuejs/vue-cli/pull/5603) docs: @babel-preset/env -> @babel/preset-env ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.4.5 (2020-06-22) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#5592](https://github.com/vuejs/vue-cli/pull/5592) fix polyfill injection when building app on multiple threads ([@dtcz](https://github.com/dtcz)) + * [#5598](https://github.com/vuejs/vue-cli/pull/5598) fix: fix an edge case that VUE_CLI_SERVICE_CONFIG_PATH might be ignored ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#5580](https://github.com/vuejs/vue-cli/pull/5580) Fix: stop ignoring --config-file cypress option ([@ahderman](https://github.com/ahderman)) +* `@vue/cli` + * [#5586](https://github.com/vuejs/vue-cli/pull/5586) fix: support auth token when retrieving package metadata ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#5528](https://github.com/vuejs/vue-cli/pull/5528) fix(nightwatch): should not install corresponding webdriver if the browser is unselected ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-shared-utils` + * [#5572](https://github.com/vuejs/vue-cli/pull/5572) refactor: replace request-promise-native with util.promisify ([@jeneser](https://github.com/jeneser)) + +#### Committers: 5 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Alexandre D'Erman ([@ahderman](https://github.com/ahderman)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Yazhe Wang ([@jeneser](https://github.com/jeneser)) +- [@dtcz](https://github.com/dtcz) + + + +## 4.4.4 (2020-06-12) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#5576](https://github.com/vuejs/vue-cli/pull/5576) fix: should return the parse result in the compiler-sfc-shim ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.4.3 (2020-06-12) + +#### :bug: Bug Fix +* `@vue/cli-plugin-eslint` + * [#5545](https://github.com/vuejs/vue-cli/pull/5545) fix(eslint-migrator): skip upgrade prompt if eslint v7 is installed (#5545) ([@EzioKissshot](https://github.com/EzioKissshot)) +* `@vue/cli-plugin-typescript` + * [#5539](https://github.com/vuejs/vue-cli/pull/5539) fix: correctly shim @vue/compiler-sfc for fork-ts-checker-plugin ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5542](https://github.com/vuejs/vue-cli/pull/5542) fix(cli-service): process the webpack failed hook in the serve command ([@jeneser](https://github.com/jeneser)) +* `@vue/cli` + * [#5540](https://github.com/vuejs/vue-cli/pull/5540) fix: add `--no-verify` to initial git commit ([@fxxjdedd](https://github.com/fxxjdedd)) + +#### :house: Internal +* `@vue/babel-preset-app` + * [#5522](https://github.com/vuejs/vue-cli/pull/5522) feat(babel-preset-app): pass full config to @babel/preset-env ([@lucaswerkmeister](https://github.com/lucaswerkmeister)) + +#### Committers: 5 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Lucas Werkmeister ([@lucaswerkmeister](https://github.com/lucaswerkmeister)) +- Zhenya Zhu ([@EzioKissshot](https://github.com/EzioKissshot)) +- fxxjdedd ([@fxxjdedd](https://github.com/fxxjdedd)) +- yazhe wang ([@jeneser](https://github.com/jeneser)) + + + +## 4.4.2 (2020-06-12) + +#### :memo: Documentation +* `@vue/cli-plugin-pwa` + * [#5530](https://github.com/vuejs/vue-cli/pull/5530) docs: mention using `null` to ignore icons ([@qirh](https://github.com/qirh)) + +#### Committers: 1 +- Saleh Alghusson ([@qirh](https://github.com/qirh)) + + + +## 4.4.1 (2020-05-25) + +#### :bug: Bug Fix +* `@vue/babel-preset-app` + * [#5513](https://github.com/vuejs/vue-cli/pull/5513) refactor: improve the polyfill importing logic of modern mode ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5502](https://github.com/vuejs/vue-cli/pull/5502) fix(cli): fix the creation log ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5408](https://github.com/vuejs/vue-cli/pull/5408) docs: explain pwa head/manifest icons ([@DRBragg](https://github.com/DRBragg)) + +#### :house: Internal +* `@vue/babel-preset-app` + * [#5513](https://github.com/vuejs/vue-cli/pull/5513) refactor: improve the polyfill importing logic of modern mode ([@sodatea](https://github.com/sodatea)) + +#### Committers: 3 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Drew Bragg ([@DRBragg](https://github.com/DRBragg)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.4.0 (2020-05-19) + +#### :rocket: New Features +* `@vue/cli` + * [#5498](https://github.com/vuejs/vue-cli/pull/5498) feat(plugin-api): expose `inquirer` to prompts.js, allowing custom prompt types ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5376](https://github.com/vuejs/vue-cli/pull/5376) feat(cli-service) add stdin flag to build ([@sickp](https://github.com/sickp)) + +#### :bug: Bug Fix +* `@vue/cli-service`, `@vue/cli-shared-utils` + * [#5500](https://github.com/vuejs/vue-cli/pull/5500) fix: should throw errors if there is bad require() in vue.config.js ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest` + * [#5499](https://github.com/vuejs/vue-cli/pull/5499) fix(unit-jest): fix .vue coverage report when babel plugin is not enabled ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5497](https://github.com/vuejs/vue-cli/pull/5497) fix: allow specifying plugin version when calling `vue add` ([@sodatea](https://github.com/sodatea)) + * [#5493](https://github.com/vuejs/vue-cli/pull/5493) fix(ui): the logs from creator should be displayed in the UI ([@sodatea](https://github.com/sodatea)) + * [#5472](https://github.com/vuejs/vue-cli/pull/5472) fix(creator): do not override the README.md generated by plugins ([@sodatea](https://github.com/sodatea)) + * [#5395](https://github.com/vuejs/vue-cli/pull/5395) Update ProjectPackageManager.js upgrade() method: manage multiple package names separated by spaces ([@motla](https://github.com/motla)) + * [#5424](https://github.com/vuejs/vue-cli/pull/5424) fix: normalize the `file` argument of `transformScript`, fix Windows compatibility ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-mocha` + * [#5473](https://github.com/vuejs/vue-cli/pull/5473) fixed --inspect-brk flag clobbering other values ([@tommyo](https://github.com/tommyo)) +* `@vue/cli-service` + * [#4800](https://github.com/vuejs/vue-cli/pull/4800) fix(serve): pass devServer sockPath properly to client ([@AlbertBrand](https://github.com/AlbertBrand)) +* `@vue/cli-plugin-eslint` + * [#5455](https://github.com/vuejs/vue-cli/pull/5455) fix(eslint): invalidate the cache when `.eslintignore` changes ([@godkun](https://github.com/godkun)) +* `@vue/cli-shared-utils` + * [#5390](https://github.com/vuejs/vue-cli/pull/5390) fix: set timeout of openChrome.applescript ([@374632897](https://github.com/374632897)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#5387](https://github.com/vuejs/vue-cli/pull/5387) [cli-plugin-e2e-nightwatch] fixing globals.js import ([@aberonni](https://github.com/aberonni)) + +#### :memo: Documentation +* Other + * [#5408](https://github.com/vuejs/vue-cli/pull/5408) docs: explain pwa head/manifest icons ([@DRBragg](https://github.com/DRBragg)) + * [#5312](https://github.com/vuejs/vue-cli/pull/5312) Make Heroku resource link accessible ([@Timibadass](https://github.com/Timibadass)) + * [#5300](https://github.com/vuejs/vue-cli/pull/5300) Update cli-service.md ([@Akenokoru](https://github.com/Akenokoru)) +* `@vue/babel-preset-app` + * [#5282](https://github.com/vuejs/vue-cli/pull/5282) docs: update polyfill names according to core-js 3 ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-plugin-vuex`, `@vue/cli-service`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui`, `@vue/cli` + * [#5496](https://github.com/vuejs/vue-cli/pull/5496) chore: dependency maintenance ([@sodatea](https://github.com/sodatea)) + +#### Committers: 14 +- Adrian B. Danieli ([@sickp](https://github.com/sickp)) +- Albert Brand ([@AlbertBrand](https://github.com/AlbertBrand)) +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Domenico Gemoli ([@aberonni](https://github.com/aberonni)) +- Drew Bragg ([@DRBragg](https://github.com/DRBragg)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Jiang Guoxi ([@374632897](https://github.com/374632897)) +- Romain ([@motla](https://github.com/motla)) +- Stefano Bartoletti ([@stefano-b](https://github.com/stefano-b)) +- Timi Omoyeni ([@Timibadass](https://github.com/Timibadass)) +- [@Akenokoru](https://github.com/Akenokoru) +- [@epixian](https://github.com/epixian) +- [@tommyo](https://github.com/tommyo) +- 杨昆 ([@godkun](https://github.com/godkun)) + + + +## 4.3.1 (2020-04-07) + +#### :bug: Bug Fix +* `@vue/cli-plugin-eslint` + * [#5363](https://github.com/vuejs/vue-cli/pull/5363) fix(eslint-migrator): fix local eslint major version detection ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5360](https://github.com/vuejs/vue-cli/pull/5360) fix: run migrator in a separator process, fix require cache issues during upgrade ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.3.0 (2020-04-01) + +#### :rocket: New Features +* `@vue/cli-plugin-unit-mocha` + * [#5294](https://github.com/vuejs/vue-cli/pull/5294) feat(service): Allow mocha unit tests debugger to be bound to a specified IP and port ([@darrylkuhn](https://github.com/darrylkuhn)) +* `@vue/babel-preset-app` + * [#5322](https://github.com/vuejs/vue-cli/pull/5322) feat: enable `bugfixes` option for babel by default ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5293](https://github.com/vuejs/vue-cli/pull/5293) support vue.config.cjs ([@simon300000](https://github.com/simon300000)) + * [#3886](https://github.com/vuejs/vue-cli/pull/3886) feat: wc entry accepts multiple file patterns splited by ',' ([@manico](https://github.com/manico)) +* `@vue/cli` + * [#5212](https://github.com/vuejs/vue-cli/pull/5212) feat(vue-cli): Choosing to save as a preset tells you where it is saved ([@jaireina](https://github.com/jaireina)) +* `@vue/cli-plugin-typescript` + * [#5170](https://github.com/vuejs/vue-cli/pull/5170) feat: use @vue/compiler-sfc as a compiler for TS if available ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli-plugin-eslint`, `@vue/cli-service-global`, `@vue/cli-ui-addon-widgets` + * [#5241](https://github.com/vuejs/vue-cli/pull/5241) feat: ease the default `no-console` severity to `warn` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui` + * [#5233](https://github.com/vuejs/vue-cli/pull/5233) feat: add "not dead" to the default browserslist query ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-router` + * [#4805](https://github.com/vuejs/vue-cli/pull/4805) types(router): added router array type for Array RouteConfig ([@manuelojeda](https://github.com/manuelojeda)) + +#### :bug: Bug Fix +* `@vue/cli-shared-utils` + * [#5315](https://github.com/vuejs/vue-cli/pull/5315) fix: avoid process hanging when trying to get Chrome version ([@sodatea](https://github.com/sodatea)) + * [#5264](https://github.com/vuejs/vue-cli/pull/5264) fix false positive of `hasProjectNpm` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui` + * [#5290](https://github.com/vuejs/vue-cli/pull/5290) fix(cli-ui): build task defaults should respect outputDir option from config file (Closes [#2639](https://github.com/vuejs/vue-cli/issues/2639)) ([@LinusBorg](https://github.com/LinusBorg)) +* `@vue/cli-service` + * [#5320](https://github.com/vuejs/vue-cli/pull/5320) fix: spawn scripts with node, fix modern mode with Yarn 2 (Berry) ([@sodatea](https://github.com/sodatea)) + * [#5247](https://github.com/vuejs/vue-cli/pull/5247) fix(target-lib): fix dynamic public path in a dynamic chunk in Firefox ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-pwa` + * [#5087](https://github.com/vuejs/vue-cli/pull/5087) feat(pwa): Check for null or undefined in iconPaths ([@janispritzkau](https://github.com/janispritzkau)) +* `@vue/cli-plugin-eslint` + * [#5242](https://github.com/vuejs/vue-cli/pull/5242) fix: fix severity config in ui ([@sodatea](https://github.com/sodatea)) +* `@vue/babel-preset-app` + * [#5236](https://github.com/vuejs/vue-cli/pull/5236) fix(babel-preset-app): avoid corejs warning when useBuiltIns is false ([@LeBenLeBen](https://github.com/LeBenLeBen)) + +#### :memo: Documentation +* [#5243](https://github.com/vuejs/vue-cli/pull/5243) docs: add warning on client side environment variables ([@sodatea](https://github.com/sodatea)) +* [#5231](https://github.com/vuejs/vue-cli/pull/5231) Update plugin-dev.md ([@yeyan1996](https://github.com/yeyan1996)) + +#### :house: Internal +* `@vue/cli-service-global` + * [#5319](https://github.com/vuejs/vue-cli/pull/5319) chore(cli-service-global): remove direct dependency on `@vue/babel-preset-app` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5305](https://github.com/vuejs/vue-cli/pull/5305) refactor: simplify config loading by skipping `fs.existsSync` check ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5228](https://github.com/vuejs/vue-cli/pull/5228) test: e2e test case for command suggestion logic ([@jamesgeorge007](https://github.com/jamesgeorge007)) + * [#5238](https://github.com/vuejs/vue-cli/pull/5238) Improve package.json not found error  ([@barbeque](https://github.com/barbeque)) + +#### :hammer: Underlying Tools +* `@vue/cli-plugin-eslint` + * [#5273](https://github.com/vuejs/vue-cli/pull/5273) chore(eslint): bump minimum required eslint-loader version to support ESLint 6 ([@megos](https://github.com/megos)) + +#### Committers: 15 +- Benoît Burgener ([@LeBenLeBen](https://github.com/LeBenLeBen)) +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Darryl Kuhn ([@darrylkuhn](https://github.com/darrylkuhn)) +- George Tsiolis ([@gtsiolis](https://github.com/gtsiolis)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Jadranko Dragoje ([@manico](https://github.com/manico)) +- Jair Reina ([@jaireina](https://github.com/jaireina)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- Janis Pritzkau ([@janispritzkau](https://github.com/janispritzkau)) +- Manuel Ojeda ([@manuelojeda](https://github.com/manuelojeda)) +- Mike ([@barbeque](https://github.com/barbeque)) +- Thorsten Lünborg ([@LinusBorg](https://github.com/LinusBorg)) +- megos ([@megos](https://github.com/megos)) +- simon3000 ([@simon300000](https://github.com/simon300000)) +- 夜宴 ([@yeyan1996](https://github.com/yeyan1996)) + + + +## 4.2.3 (2020-02-27) + +#### :bug: Bug Fix +* `@vue/cli` + * [#5163](https://github.com/vuejs/vue-cli/pull/5163) fix "Vue packages version mismatch" error caused by other global packages ([@sodatea](https://github.com/sodatea)) + * [#5202](https://github.com/vuejs/vue-cli/pull/5202) fix(GeneratorAPI): remove warning when using extendPackage with prune ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli-service-global` + * [#5196](https://github.com/vuejs/vue-cli/pull/5196) fix(cli-service-global): fix no-debugger rule config ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5224](https://github.com/vuejs/vue-cli/pull/5224) Update mode-and-env.md ([@derline](https://github.com/derline)) +* [#5184](https://github.com/vuejs/vue-cli/pull/5184) Remove unnecessary hyphen ([@dehero](https://github.com/dehero)) +* [#5209](https://github.com/vuejs/vue-cli/pull/5209) docs(zh): update example format ([@defead](https://github.com/defead)) +* [#5141](https://github.com/vuejs/vue-cli/pull/5141) docs(zh): Update now 404 url ([@xiaohp](https://github.com/xiaohp)) +* [#5176](https://github.com/vuejs/vue-cli/pull/5176) Added basic upgrading instructions ([@Uninen](https://github.com/Uninen)) +* [#5157](https://github.com/vuejs/vue-cli/pull/5157) docs(zh): fix typos ([@maomao1996](https://github.com/maomao1996)) + +#### :house: Internal +* `@vue/cli` + * [#5166](https://github.com/vuejs/vue-cli/pull/5166) chore: switch over to leven for command suggestion ([@jamesgeorge007](https://github.com/jamesgeorge007)) + +#### Committers: 9 +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- Ville Säävuori ([@Uninen](https://github.com/Uninen)) +- Xiao Haiping ([@xiaohp](https://github.com/xiaohp)) +- [@defead](https://github.com/defead) +- [@dehero](https://github.com/dehero) +- [@derline](https://github.com/derline) +- 茂茂 ([@maomao1996](https://github.com/maomao1996)) + + +## 4.2.2 (2020-02-07) + +#### :bug: Bug Fix +* `@vue/cli` + * [0d0168b](https://github.com/vuejs/vue-cli/commit/0d0168b) fix(ui): fix the incorrect RegExp used for CORS check ([@sodatea](https://github.com/sodatea)) + +## 4.2.1 (2020-02-07) + +#### :bug: Bug Fix +* `@vue/cli-ui` + * [776275d](https://github.com/vuejs/vue-cli/commit/776275d) fix: add graphql-server.js to npm files ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#5126](https://github.com/vuejs/vue-cli/pull/5126) fix(docs): new travis CLI interface ([@iliyaZelenko](https://github.com/iliyaZelenko)) +* [#5122](https://github.com/vuejs/vue-cli/pull/5122) Add a demo for multiple loaders (Chinese doc) ([@FrankFang](https://github.com/FrankFang)) +* [#5094](https://github.com/vuejs/vue-cli/pull/5094) docs: [RU] Translation update ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +* [#5081](https://github.com/vuejs/vue-cli/pull/5081) line 47 according to english version ([@defead](https://github.com/defead)) +* [#5076](https://github.com/vuejs/vue-cli/pull/5076) Add a demo for multiple loaders ([@FrankFang](https://github.com/FrankFang)) +* [#5079](https://github.com/vuejs/vue-cli/pull/5079) Mention that Vue CLI should be installed in Prototyping guide ([@NataliaTepluhina](https://github.com/NataliaTepluhina)) +* [#5078](https://github.com/vuejs/vue-cli/pull/5078) Fix a typo in migration guide ([@NataliaTepluhina](https://github.com/NataliaTepluhina)) +* [#5055](https://github.com/vuejs/vue-cli/pull/5055) docs: mention the precedence of `.vue` & `.ts(x)` extensions ([@sodatea](https://github.com/sodatea)) +* [#5019](https://github.com/vuejs/vue-cli/pull/5019) Updated zh-cn translation in cli section ([@mactanxin](https://github.com/mactanxin)) + +#### Committers: 8 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Frank Fang ([@FrankFang](https://github.com/FrankFang)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Natalia Tepluhina ([@NataliaTepluhina](https://github.com/NataliaTepluhina)) +- Xin Tan ([@mactanxin](https://github.com/mactanxin)) +- [@defead](https://github.com/defead) +- Илья ([@iliyaZelenko](https://github.com/iliyaZelenko)) +- 小新 ([@llccing](https://github.com/llccing)) + + + +## 4.2.0 (2020-02-07) + +#### :rocket: New Features +* `@vue/cli-plugin-babel`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli` + * [#5149](https://github.com/vuejs/vue-cli/pull/5149) feat(GeneratorAPI): allow passing options to `api.extendPackage` ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha` + * [#5147](https://github.com/vuejs/vue-cli/pull/5147) feat: create projects with @vue/test-utils beta 31 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui`, `@vue/cli` + * [#5134](https://github.com/vuejs/vue-cli/pull/5134) feat: lock minor versions when creating projects / adding plugins ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-typescript`, `@vue/cli-ui` + * [#5128](https://github.com/vuejs/vue-cli/pull/5128) feat: upgrade to typescript@~3.7.5 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5091](https://github.com/vuejs/vue-cli/pull/5091) feat: `vue upgrade` monorepo support, `--from` option, and a new `vue migrate --from` command ([@sodatea](https://github.com/sodatea)) + * [#4828](https://github.com/vuejs/vue-cli/pull/4828) feat: add option `--merge` to `create` command ([@zyy7259](https://github.com/zyy7259)) +* `@vue/cli-service` + * [#4953](https://github.com/vuejs/vue-cli/pull/4953) feat: adds transparent PnP support to Webpack ([@arcanis](https://github.com/arcanis)) + * [#2411](https://github.com/vuejs/vue-cli/pull/2411) feat(cli): add `--stdin` flag to serve ([@nullpilot](https://github.com/nullpilot)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-pwa`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui`, `@vue/cli` + * [#4933](https://github.com/vuejs/vue-cli/pull/4933) feat: upgrade to eslint 6 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-shared-utils`, `@vue/cli` + * [#4827](https://github.com/vuejs/vue-cli/pull/4827) feat: respect existing package.json ([@zyy7259](https://github.com/zyy7259)) +* `@vue/babel-preset-app` + * [#4959](https://github.com/vuejs/vue-cli/pull/4959) feat: specify babel runtime version ([@zyy7259](https://github.com/zyy7259)) +* `@vue/cli-service-global` + * [#5029](https://github.com/vuejs/vue-cli/pull/5029) feat: don't throw on console/debugger statements for `vue serve` ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-shared-utils`, `@vue/cli` + * [#5150](https://github.com/vuejs/vue-cli/pull/5150) fix: should infer package manager from config if there's no lockfile in the project ([@sodatea](https://github.com/sodatea)) + * [#5045](https://github.com/vuejs/vue-cli/pull/5045) refactor: use a plain http request to get package metadata ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5062](https://github.com/vuejs/vue-cli/pull/5062) fix `afterInvoke`/`onCreateComplete` callbacks in Migrator ([@sodatea](https://github.com/sodatea)) + * [#5038](https://github.com/vuejs/vue-cli/pull/5038) fix: `extendPackage` dependency versions should be string ([@pksunkara](https://github.com/pksunkara)) +* `@vue/cli-ui`, `@vue/cli` + * [#4985](https://github.com/vuejs/vue-cli/pull/4985) fix(CORS): only allow connections from the designated host ([@Akryum](https://github.com/Akryum)) + * [#5142](https://github.com/vuejs/vue-cli/pull/5142) fix(CORS): fixup #4985, allow same-origin ws requests of any domain ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-cypress` + * [#5108](https://github.com/vuejs/vue-cli/pull/5108) fix(e2e-cypress): make `--headless` work with `--browser chrome` ([@LinusBorg](https://github.com/LinusBorg)) + * [#4910](https://github.com/vuejs/vue-cli/pull/4910) fix: comment eslint disable in cypress config ([@cexbrayat](https://github.com/cexbrayat)) +* `@vue/cli-service` + * [#5113](https://github.com/vuejs/vue-cli/pull/5113) fix: correctly calculate cacheIdentifier from lockfiles ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-pwa` + * [#5089](https://github.com/vuejs/vue-cli/pull/5089) fix: pwa-plugin avoid generating manifest when path is an URL ([@tkint](https://github.com/tkint)) +* `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha` + * [#5028](https://github.com/vuejs/vue-cli/pull/5028) fix applyESLint when eslint plugin is added after unit test plugins ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-test-utils` + * [#5069](https://github.com/vuejs/vue-cli/pull/5069) Use a single websocket connection for HMR ([@lbogdan](https://github.com/lbogdan)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#5016](https://github.com/vuejs/vue-cli/pull/5016) fix(e2e-nightwatch): check for correct flag name ([@LinusBorg](https://github.com/LinusBorg)) + +#### :memo: Documentation +* [#5019](https://github.com/vuejs/vue-cli/pull/5019) Updated zh-cn translation in cli section ([@mactanxin](https://github.com/mactanxin)) + +#### :house: Internal +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel` + * [#5133](https://github.com/vuejs/vue-cli/pull/5133) refactor: remove usage of deprecated babel functions, preparing for babel 8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#5123](https://github.com/vuejs/vue-cli/pull/5123) fix: `vue-template-compiler` can be optional if `@vue/compiler-sfc` presents ([@sodatea](https://github.com/sodatea)) + * [#5060](https://github.com/vuejs/vue-cli/pull/5060) refactor: use the `title` option in the html template, instead of hard-code the project name ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#5110](https://github.com/vuejs/vue-cli/pull/5110) refactor: use env variables to set registry for package managers ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-shared-utils` + * [#5092](https://github.com/vuejs/vue-cli/pull/5092) refactor: use `createRequire` to load/resolve modules ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-service` + * [#4991](https://github.com/vuejs/vue-cli/pull/4991) 🎨 style: unified components' naming style ([@taoweicn](https://github.com/taoweicn)) + +#### Committers: 17 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Bogdan Luca ([@lbogdan](https://github.com/lbogdan)) +- Cédric Exbrayat ([@cexbrayat](https://github.com/cexbrayat)) +- Dan Hogan ([@danhogan](https://github.com/danhogan)) +- Daniel Bächtold ([@danbaechtold](https://github.com/danbaechtold)) +- Eduardo San Martin Morote ([@posva](https://github.com/posva)) +- Guillaume Chau ([@Akryum](https://github.com/Akryum)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Marcel Lindig ([@nullpilot](https://github.com/nullpilot)) +- Maël Nison ([@arcanis](https://github.com/arcanis)) +- Pavan Kumar Sunkara ([@pksunkara](https://github.com/pksunkara)) +- Tao Wei ([@taoweicn](https://github.com/taoweicn)) +- Thomas Kint ([@tkint](https://github.com/tkint)) +- Thorsten Lünborg ([@LinusBorg](https://github.com/LinusBorg)) +- Xin Tan ([@mactanxin](https://github.com/mactanxin)) +- Yingya Zhang ([@zyy7259](https://github.com/zyy7259)) +- plantainX ([@cheqianxiao](https://github.com/cheqianxiao)) + + + +## 4.1.2 (2019-12-28) + +#### :bug: Bug Fix +* `@vue/cli-plugin-pwa` + * [#4974](https://github.com/vuejs/vue-cli/pull/4974) fix: fix several bugs in the PWA plugin UI, make it usable again ([@sodatea](https://github.com/sodatea)) +* `@vue/cli` + * [#4922](https://github.com/vuejs/vue-cli/pull/4922) fix: should download to different directories for different presets ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-babel` + * [#4924](https://github.com/vuejs/vue-cli/pull/4924) fix: do not throw when babel config contains ignore/include/exclude ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* `@vue/cli-service-global` + * [#5004](https://github.com/vuejs/vue-cli/pull/5004) build: fix link to homepage ([@Scrum](https://github.com/Scrum)) +* `@vue/cli-plugin-unit-jest` + * [#4754](https://github.com/vuejs/vue-cli/pull/4754) Update debugging instructions ([@zigomir](https://github.com/zigomir)) +* Other + * [#4976](https://github.com/vuejs/vue-cli/pull/4976) docs: his -> their ([@sodatea](https://github.com/sodatea)) + * [#4973](https://github.com/vuejs/vue-cli/pull/4973) docs: mention navigateFallback option for PWA App Shell caching ([@clementmas](https://github.com/clementmas)) + * [#4917](https://github.com/vuejs/vue-cli/pull/4917) docs: [RU] Translation update ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) + +#### :house: Internal +* `@vue/cli` + * [#4904](https://github.com/vuejs/vue-cli/pull/4904) refactor: use inline approach ([@jamesgeorge007](https://github.com/jamesgeorge007)) +* `@vue/cli-service` + * [#4909](https://github.com/vuejs/vue-cli/pull/4909) changed var-name `async` to `isAsync` ([@ikumargaurav](https://github.com/ikumargaurav)) + +#### Committers: 9 +- Alexander Sokolov ([@Alex-Sokolov](https://github.com/Alex-Sokolov)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Ivan Demidov ([@Scrum](https://github.com/Scrum)) +- James George ([@jamesgeorge007](https://github.com/jamesgeorge007)) +- Jorge Moliner ([@whoisjorge](https://github.com/whoisjorge)) +- Jun-Kyu Kim ([@x6ax6b](https://github.com/x6ax6b)) +- Kumar Gaurav ([@ikumargaurav](https://github.com/ikumargaurav)) +- clem ([@clementmas](https://github.com/clementmas)) +- ziga ([@zigomir](https://github.com/zigomir)) + + + +## 4.1.1 (2019-11-27) + +#### :bug: Bug Fix +* `@vue/cli-plugin-typescript` + * [#4894](https://github.com/vuejs/vue-cli/pull/4894) fix: fix tsx compilation ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.1.0 (2019-11-27) + +#### :rocket: New Features +* `@vue/cli-plugin-pwa` + * [#4736](https://github.com/vuejs/vue-cli/pull/4736) feat: allow use of full url for pwa manifest and icons ([@tkint](https://github.com/tkint)) + +#### :bug: Bug Fix +* `@vue/cli-shared-utils` + * [#4842](https://github.com/vuejs/vue-cli/pull/4842) Replace chalk.reset with stripAnsi in @vue/cli-shared-utils/lib/logger.js ([@perakerberg](https://github.com/perakerberg)) +* `@vue/cli` + * [#4883](https://github.com/vuejs/vue-cli/pull/4883) fix: support `parser` option for codemods, and enable ts parsing by default ([@sodatea](https://github.com/sodatea)) + * [#4859](https://github.com/vuejs/vue-cli/pull/4859) fix: invalid version error when modules not installed ([@yannbertrand](https://github.com/yannbertrand)) + +#### :memo: Documentation +* [#4820](https://github.com/vuejs/vue-cli/pull/4820) Update doc section on Git Hooks ([@Codermar](https://github.com/Codermar)) +* [#4836](https://github.com/vuejs/vue-cli/pull/4836) docs: add warnings on CSS sideEffects ([@sodatea](https://github.com/sodatea)) +* [#4831](https://github.com/vuejs/vue-cli/pull/4831) Update browser-compatibility.md ([@wenhandi](https://github.com/wenhandi)) +* [#4716](https://github.com/vuejs/vue-cli/pull/4716) use gitlab CI env variable for project name ([@gregoiredx](https://github.com/gregoiredx)) +* [#4803](https://github.com/vuejs/vue-cli/pull/4803) fix docs `css.loaderOptions.css.localsConvention` ([@negibouze](https://github.com/negibouze)) +* [#4746](https://github.com/vuejs/vue-cli/pull/4746) Update migrating-from-v3 README typo ([@seangwright](https://github.com/seangwright)) + +#### Committers: 11 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Jose G. Alfonso ([@Codermar](https://github.com/Codermar)) +- Per Åkerberg ([@perakerberg](https://github.com/perakerberg)) +- Sean G. Wright ([@seangwright](https://github.com/seangwright)) +- Thomas Kint ([@tkint](https://github.com/tkint)) +- Yann Bertrand ([@yannbertrand](https://github.com/yannbertrand)) +- Yingya Zhang ([@zyy7259](https://github.com/zyy7259)) +- Yoshiaki Itakura ([@negibouze](https://github.com/negibouze)) +- [@arnaudvalle](https://github.com/arnaudvalle) +- [@gregoiredx](https://github.com/gregoiredx) +- 文翰弟 ([@wenhandi](https://github.com/wenhandi)) + + + +## 4.1.0-beta.0 (2019-11-09) + +#### :rocket: New Features +* `@vue/cli` + * [#4715](https://github.com/vuejs/vue-cli/pull/4715) feat(GeneratorAPI): accept multiple arguments for the resolve method ([@sodatea](https://github.com/sodatea)) + * [#4767](https://github.com/vuejs/vue-cli/pull/4767) feat: support binary mirrors for taobao registry ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui`, `@vue/cli` + * [#4798](https://github.com/vuejs/vue-cli/pull/4798) feat: enable postcss+autoprefixer by default internally, reducing boilerplate ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4816](https://github.com/vuejs/vue-cli/pull/4816) fix: don't prepend publicPath with slash ([@sodatea](https://github.com/sodatea)) + * [#4809](https://github.com/vuejs/vue-cli/pull/4809) fix: fix build error when path contains space (Closes [#4667](https://github.com/vuejs/vue-cli/issues/4667)) ([@RSeidelsohn](https://github.com/RSeidelsohn)) +* `@vue/babel-preset-app` + * [#4797](https://github.com/vuejs/vue-cli/pull/4797) fix: add `sourceType: 'unambiguous'` to babel preset ([@sodatea](https://github.com/sodatea)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-service` + * [#4777](https://github.com/vuejs/vue-cli/pull/4777) refactor: use babel overrides to transpile babel runtime helpers ([@sodatea](https://github.com/sodatea)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-typescript`, `@vue/cli-service`, `@vue/cli-ui` + * [#4532](https://github.com/vuejs/vue-cli/pull/4532) Enforces require.resolve for loaders ([@arcanis](https://github.com/arcanis)) + +#### :memo: Documentation +* [#4760](https://github.com/vuejs/vue-cli/pull/4760) Add 'Browse plugins' link to header ([@Akryum](https://github.com/Akryum)) + +#### :house: Internal +* `@vue/cli-ui` + * [#4818](https://github.com/vuejs/vue-cli/pull/4818) Add missing cli-ui dependencies ([@JanCVanB](https://github.com/JanCVanB)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-service` + * [#4777](https://github.com/vuejs/vue-cli/pull/4777) refactor: use babel overrides to transpile babel runtime helpers ([@sodatea](https://github.com/sodatea)) + +#### Committers: 5 +- Guillaume Chau ([@Akryum](https://github.com/Akryum)) +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Jan Van Bruggen ([@JanCVanB](https://github.com/JanCVanB)) +- Maël Nison ([@arcanis](https://github.com/arcanis)) +- Roman Seidelsohn ([@RSeidelsohn](https://github.com/RSeidelsohn)) + + + +## 4.0.5 (2019-10-22) + +#### :bug: Bug Fix +* `@vue/cli` + * [#4741](https://github.com/vuejs/vue-cli/pull/4741) fix: should tolerate cli version check error ([@sodatea](https://github.com/sodatea)) + * [#4720](https://github.com/vuejs/vue-cli/pull/4720) fix: do not install core plugins that have major version bumps ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint` + * [#4740](https://github.com/vuejs/vue-cli/pull/4740) fix(eslint): autofix code style after scaffolding on older versions of cli ([@sodatea](https://github.com/sodatea)) + * [#4728](https://github.com/vuejs/vue-cli/pull/4728) fix: fix eslint not found error in `vue serve` command ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-ui` + * [#4739](https://github.com/vuejs/vue-cli/pull/4739) fix(ui): "add router" button should not require prompt in terminal ([@sodatea](https://github.com/sodatea)) + * [#4724](https://github.com/vuejs/vue-cli/pull/4724) fix(ui): fix latest version check always displaying "0.1.0" ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#4733](https://github.com/vuejs/vue-cli/pull/4733) Fix indentation of --inline-vue description ([@mul14](https://github.com/mul14)) + +#### :house: Internal +* `@vue/babel-preset-app`, `@vue/cli-plugin-babel`, `@vue/cli-plugin-e2e-cypress`, `@vue/cli-plugin-e2e-nightwatch`, `@vue/cli-plugin-eslint`, `@vue/cli-plugin-router`, `@vue/cli-plugin-typescript`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha`, `@vue/cli-service-global`, `@vue/cli-service`, `@vue/cli-test-utils`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui`, `@vue/cli` + * [#4734](https://github.com/vuejs/vue-cli/pull/4734) chore: dependency maintenance ([@sodatea](https://github.com/sodatea)) + +#### Committers: 2 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Mulia Nasution ([@mul14](https://github.com/mul14)) + + + +## 4.0.4 (2019-10-18) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4711](https://github.com/vuejs/vue-cli/pull/4711) fix: fix a typo that caused router failed to install in older versions ([@sodatea](https://github.com/sodatea)) + +#### :memo: Documentation +* [#4702](https://github.com/vuejs/vue-cli/pull/4702) Fix link to eslint PR ([@rmbl](https://github.com/rmbl)) + +#### Committers: 2 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Philipp Gildein ([@rmbl](https://github.com/rmbl)) + + + +## 4.0.3 (2019-10-17) + +#### :bug: Bug Fix +* `@vue/cli-ui`, `@vue/cli` + * [#4698](https://github.com/vuejs/vue-cli/pull/4698) fix: fix `vue add router` command in v3 projects ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#4696](https://github.com/vuejs/vue-cli/pull/4696) fix: allow v3 cli to invoke vuex & router plugin from inside cli-service ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-eslint`, `@vue/cli-plugin-typescript`, `@vue/cli-ui` + * [#4697](https://github.com/vuejs/vue-cli/pull/4697) fix: fix "lint on commit" projects generation error ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.0.2 (2019-10-17) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4693](https://github.com/vuejs/vue-cli/pull/4693) fix: add a compatibility layer for router & vuex for CLI v3 ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + +## 4.0.1 (2019-10-16) + +#### :bug: Bug Fix + +* `@vue/cli-plugin-eslint`, `@vue/cli-plugin-router`, `@vue/cli-plugin-vuex`, `@vue/cli-service-global`, `@vue/cli-ui-addon-webpack`, `@vue/cli-ui-addon-widgets`, `@vue/cli-ui`, `@vue/cli` + * [fec160f](https://github.com/vuejs/vue-cli/commit/fec160ff964964bc71aa857d21d0614284fa2fdb) fix: no need to assertCliVersion. avoid breaking old versions ([@sodatea](https://github.com/sodatea)) + + +## 4.0.0 (2019-10-16) + +#### :rocket: New Features +* `@vue/cli-shared-utils`, `@vue/cli` + * [#4677](https://github.com/vuejs/vue-cli/pull/4677) fix: add pnpm v4 support ([@B4rtware](https://github.com/B4rtware)) + +#### :boom: Breaking Changes +* `@vue/cli` + * [#4681](https://github.com/vuejs/vue-cli/pull/4681) chore!: add `@vue/cli` in `--version` output, to avoid confusion ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-plugin-babel` + * [#4683](https://github.com/vuejs/vue-cli/pull/4683) fix: Corrected typo in babel migrator ([@nblackburn](https://github.com/nblackburn)) + +#### :memo: Documentation +* [#2319](https://github.com/vuejs/vue-cli/pull/2319) missing documentation for building with vuex ([@katerlouis](https://github.com/katerlouis)) + +#### Committers: 5 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Maël Nison ([@arcanis](https://github.com/arcanis)) +- Nathaniel Blackburn ([@nblackburn](https://github.com/nblackburn)) +- René Eschke ([@katerlouis](https://github.com/katerlouis)) +- [@B4rtware](https://github.com/B4rtware) + + + +## 4.0.0-rc.8 (2019-10-11) + +#### :rocket: New Features +* `@vue/cli` + * [#3926](https://github.com/vuejs/vue-cli/pull/3926) chore: better upgrade messages ([@phanan](https://github.com/phanan)) +* `@vue/babel-preset-app`, `@vue/cli-plugin-unit-jest`, `@vue/cli-plugin-unit-mocha` + * [#4663](https://github.com/vuejs/vue-cli/pull/4663) feat(babel-preset): set target to node whenever NODE_ENV === 'test' ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-pwa` + * [#4664](https://github.com/vuejs/vue-cli/pull/4664) feat(pwa): improve compatibility with v3 plugin usage ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-service` + * [#4641](https://github.com/vuejs/vue-cli/pull/4641) feat: make the minimizer config available in all modes ([@sodatea](https://github.com/sodatea)) + * [#4644](https://github.com/vuejs/vue-cli/pull/4644) feat: add webdriver log files to gitignore ([@sodatea](https://github.com/sodatea)) + +#### :boom: Breaking Changes +* `@vue/cli-service` + * [#4676](https://github.com/vuejs/vue-cli/pull/4676) chore!: upgrade terser-webpack-plugin to 2.x ([@sodatea](https://github.com/sodatea)) + * [#4673](https://github.com/vuejs/vue-cli/pull/4673) refactor!: use DefinePlugin (again) instead of EnvironmentPlugin ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4666](https://github.com/vuejs/vue-cli/pull/4666) fix: fix redundant log messages from webpack-dev-server ([@sodatea](https://github.com/sodatea)) + +#### :house: Internal +* `@vue/cli-service` + * [#4673](https://github.com/vuejs/vue-cli/pull/4673) refactor!: use DefinePlugin (again) instead of EnvironmentPlugin ([@sodatea](https://github.com/sodatea)) + +#### :hammer: Underlying Tools +* `@vue/cli-service` + * [#4676](https://github.com/vuejs/vue-cli/pull/4676) chore!: upgrade terser-webpack-plugin to 2.x ([@sodatea](https://github.com/sodatea)) + +#### Committers: 2 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Phan An ([@phanan](https://github.com/phanan)) + + + +## 4.0.0-rc.7 (2019-10-01) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4637](https://github.com/vuejs/vue-cli/pull/4637) fix: avoid accidentally overriding sass config with scss configs ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.0.0-rc.6 (2019-09-30) + +#### :rocket: New Features +* `@vue/cli-plugin-babel` + * [#4633](https://github.com/vuejs/vue-cli/pull/4633) feat(babel-migrator): transform babel preset regardless of plugin version ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli` + * [#4634](https://github.com/vuejs/vue-cli/pull/4634) fix(upgrade-all): avoid accidentally writing outdated package.json back ([@sodatea](https://github.com/sodatea)) + +#### Committers: 1 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) + + + +## 4.0.0-rc.5 (2019-09-30) + +#### :rocket: New Features +* `@vue/cli` + * [#4621](https://github.com/vuejs/vue-cli/pull/4621) feat: support custom package manager ([@zyy7259](https://github.com/zyy7259)) +* `@vue/cli-plugin-babel` + * [#4629](https://github.com/vuejs/vue-cli/pull/4629) feat(babel): transform preset names in the plugin migrator ([@sodatea](https://github.com/sodatea)) + +#### :bug: Bug Fix +* `@vue/cli-service` + * [#4631](https://github.com/vuejs/vue-cli/pull/4631) fix: fix sassOptions merging for scss syntax in sass-loader v8 ([@sodatea](https://github.com/sodatea)) +* `@vue/cli-plugin-e2e-nightwatch` + * [#4622](https://github.com/vuejs/vue-cli/pull/4622) fix: fix nightwatch template's compatibility with eslint plugin ([@sodatea](https://github.com/sodatea)) + * [#4627](https://github.com/vuejs/vue-cli/pull/4627) fix: fix nightwatch cli option (`--url`) handling ([@sodatea](https://github.com/sodatea)) + +#### Committers: 2 +- Haoqun Jiang ([@sodatea](https://github.com/sodatea)) +- Yingya Zhang ([@zyy7259](https://github.com/zyy7259)) + + + ## 4.0.0-rc.4 (2019-09-25) Start from the version, the `unit-jest` plugin comes with 4 configuration presets: @@ -956,7 +2883,7 @@ Most of the following new features and bugfixes also applies to v3.8.0 * [#1531](https://github.com/vuejs/vue-cli/pull/1531) support PNPM as a package manager ([@robertkruis](https://github.com/robertkruis)) * [#3790](https://github.com/vuejs/vue-cli/pull/3790) fix PNPM compatibility issues during scaffolding ([@sodatea](https://github.com/sodatea)) * `@vue/cli-plugin-eslint`, `@vue/cli-service` - * [#3572](https://github.com/vuejs/vue-cli/pull/3572) add 3rd option to `lintOnSave` to support 'default' behaviour (Closes [#3552](https://github.com/vuejs/vue-cli/issues/3552)) ([@LinusBorg](https://github.com/LinusBorg)) + * [#3572](https://github.com/vuejs/vue-cli/pull/3572) add 3rd option to `lintOnSave` to support 'default' behavior (Closes [#3552](https://github.com/vuejs/vue-cli/issues/3552)) ([@LinusBorg](https://github.com/LinusBorg)) * `@vue/cli-plugin-unit-jest` * [#3589](https://github.com/vuejs/vue-cli/pull/3589) add jest typeahead plugin ([@sodatea](https://github.com/sodatea)) @@ -2553,7 +4480,7 @@ will need to explicitly install `typescript` in your project. * **ui:** improved remote preset checking ([0ba5e09](https://github.com/vuejs/vue-cli/commit/0ba5e09)) * **ui:** list item hover background more subtle ([a5bb260](https://github.com/vuejs/vue-cli/commit/a5bb260)) * **ui:** more spacing in status bar ([80a847f](https://github.com/vuejs/vue-cli/commit/80a847f)) -* **ui:** project create detials: bigger grid gap ([cfed833](https://github.com/vuejs/vue-cli/commit/cfed833)) +* **ui:** project create details: bigger grid gap ([cfed833](https://github.com/vuejs/vue-cli/commit/cfed833)) * **ui:** project creation not reset ([9efdfaf](https://github.com/vuejs/vue-cli/commit/9efdfaf)) * **ui:** remove console.log ([04d76a2](https://github.com/vuejs/vue-cli/commit/04d76a2)) * **ui:** reset webpack.config.js service on correct CWD, closes [#1555](https://github.com/vuejs/vue-cli/issues/1555) ([dc2f8e8](https://github.com/vuejs/vue-cli/commit/dc2f8e8)) @@ -3443,7 +5370,7 @@ sepcify the default mode for a registered command, the plugins should expose * **ui:** PluginAdd tab check ([ca01d95](https://github.com/vuejs/vue-cli/commit/ca01d95)) * **ui:** pormpts remove result in answers when disabled ([a29a3b4](https://github.com/vuejs/vue-cli/commit/a29a3b4)) * **ui:** stderr new lines + selected task status color ([b949406](https://github.com/vuejs/vue-cli/commit/b949406)) -* **ui:** progress handler should not throw error (casuing process to exit) ([3d4d8f0](https://github.com/vuejs/vue-cli/commit/3d4d8f0)) +* **ui:** progress handler should not throw error (causing process to exit) ([3d4d8f0](https://github.com/vuejs/vue-cli/commit/3d4d8f0)) * **ui:** ProjectNav padding ([4fd8885](https://github.com/vuejs/vue-cli/commit/4fd8885)) * **ui:** ProjectNavButton tooltip delay ([131cc46](https://github.com/vuejs/vue-cli/commit/131cc46)) * **ui:** prompt margins ([100a12e](https://github.com/vuejs/vue-cli/commit/100a12e)) @@ -3519,7 +5446,7 @@ sepcify the default mode for a registered command, the plugins should expose #### Bug Fixes -* **ui:** deps + dahsboard plugin ([a628b43](https://github.com/vuejs/vue-cli/commit/a628b43)) +* **ui:** deps + dashboard plugin ([a628b43](https://github.com/vuejs/vue-cli/commit/a628b43)) * **ui:** display 0 instead of NaN ([21d3e94](https://github.com/vuejs/vue-cli/commit/21d3e94)) #### Features diff --git a/README.md b/README.md index 450f20e08e..0962eaf2fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ -# vue-cli [![Build Status](https://circleci.com/gh/vuejs/vue-cli/tree/dev.svg?style=shield)](https://circleci.com/gh/vuejs/vue-cli/tree/dev) [![Windows Build status](https://ci.appveyor.com/api/projects/status/rkpafdpdwie9lqx0/branch/dev?svg=true)](https://ci.appveyor.com/project/yyx990803/vue-cli/branch/dev) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) +# Vue CLI [![Build Status](https://circleci.com/gh/vuejs/vue-cli/tree/dev.svg?style=shield)](https://circleci.com/gh/vuejs/vue-cli/tree/dev) [![Windows Build status](https://ci.appveyor.com/api/projects/status/rkpafdpdwie9lqx0/branch/dev?svg=true)](https://ci.appveyor.com/project/yyx990803/vue-cli/branch/dev) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) +## ⚠️ Status -> Vue CLI is the Standard Tooling for Vue.js Development. +Vue CLI is now in maintenance mode. For new projects, please use [create-vue](https://github.com/vuejs/create-vue) to scaffold [Vite](https://vitejs.dev/)-based projects. `create-vue` supports both Vue 2 and Vue 3. + +Also refer to the [Vue 3 Tooling Guide](https://vuejs.org/guide/scaling-up/tooling.html) for the latest recommendations. + +For information on migrating from Vue CLI to Vite: + +- [Vue CLI -> Vite Migration Guide from VueSchool.io](https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vue-cli-to-vite/) +- [Tools / Plugins that help with auto migration](https://github.com/vitejs/awesome-vite#vue-cli) ## Documentation diff --git a/__mocks__/inquirer.js b/__mocks__/inquirer.js index 70384c8597..1ff8fa9c7b 100644 --- a/__mocks__/inquirer.js +++ b/__mocks__/inquirer.js @@ -7,7 +7,7 @@ exports.prompt = prompts => { const answers = {} let skipped = 0 - prompts.forEach((prompt, i) => { + prompts.forEach((prompt, index) => { if (prompt.when && !prompt.when(answers)) { skipped++ return @@ -25,7 +25,7 @@ exports.prompt = prompts => { : val } - const a = pendingAssertions[i - skipped] + const a = pendingAssertions[index - skipped] if (!a) { console.error(`no matching assertion for prompt:`, prompt) console.log(prompts) @@ -88,6 +88,10 @@ exports.prompt = prompts => { return Promise.resolve(answers) } +exports.createPromptModule = () => { + return exports.prompt +} + exports.expectPrompts = assertions => { pendingAssertions = assertions } diff --git a/appveyor.yml b/appveyor.yml index eb75f20c73..f54d23cd69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ environment: - nodejs_version: "10" + nodejs_version: "14" install: - ps: Install-Product node $env:nodejs_version @@ -10,12 +10,13 @@ test_script: - git --version - node --version - yarn --version - - yarn test + - yarn test --testPathIgnorePatterns globalService # ui tests temporarily disabled due to Cypress 3.0 issue on windows # - cd "packages/@vue/cli-ui" && yarn test cache: - - node_modules -> yarn.lock + - node_modules -> appveyor.yml, **\package.json, yarn.lock + - '%LOCALAPPDATA%\Yarn -> appveyor.yml, **\package.json, yarn.lock' build: off diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js new file mode 100644 index 0000000000..06543eaa18 --- /dev/null +++ b/docs/.vitepress/config.js @@ -0,0 +1,667 @@ +const fs = require('fs') +const path = require('path') + +const selfDestroyingSWVitePlugin = { + name: 'generate-self-destroying-service-worker', + buildStart() { + this.emitFile({ + type: 'asset', + fileName: 'service-worker.js', + source: fs.readFileSync(path.join(__dirname, './self-destroying-service-worker.js'), 'utf-8') + }) + } +} + +module.exports = { + vite: { + // to destroy the service worker used by the previous vuepress build + plugins: [selfDestroyingSWVitePlugin] + }, + + locales: { + '/': { + lang: 'en-US', + title: 'Vue CLI', + description: '🛠️ Standard Tooling for Vue.js Development' + }, + '/zh/': { + lang: 'zh-CN', + title: 'Vue CLI', + description: '🛠️ Vue.js 开发的标准工具' + }, + '/ru/': { + lang: 'ru', + title: 'Vue CLI', + description: '🛠️ Стандартный инструментарий для разработки на Vue.js' + } + }, + + head: [ + ['link', { rel: 'icon', href: '/favicon.png' }], + ['link', { rel: 'manifest', href: '/manifest.json' }], + ['meta', { name: 'theme-color', content: '#3eaf7c' }], + ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], + [ + 'meta', + { name: 'apple-mobile-web-app-status-bar-style', content: 'black' } + ], + [ + 'link', + { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` } + ], + [ + 'link', + { + rel: 'mask-icon', + href: '/icons/safari-pinned-tab.svg', + color: '#3eaf7c' + } + ], + [ + 'meta', + { + name: 'msapplication-TileImage', + content: '/icons/msapplication-icon-144x144.png' + } + ], + ['meta', { name: 'msapplication-TileColor', content: '#000000' }] + ], + + themeConfig: { + repo: 'vuejs/vue-cli', + docsDir: 'docs', + docsBranch: 'master', + editLinks: true, + + algolia: { + indexName: 'cli_vuejs', + apiKey: 'f6df220f7d246aff64a56300b7f19f21' + }, + + carbonAds: { + carbon: 'CEBDT27Y', + placement: 'vuejsorg' + }, + + locales: { + '/': { + label: 'English', + selectText: 'Languages', + lastUpdated: 'Last Updated', + editLinkText: 'Edit this page on GitHub', + nav: [ + { + text: 'Guide', + link: '/guide/' + }, + { + text: 'Config Reference', + link: '/config/' + }, + { + text: 'Plugins', + items: [ + { + text: 'Core Plugins', + link: '/core-plugins/' + }, + { + text: 'Plugin Dev Guide', + link: '/dev-guide/plugin-dev' + }, + { + text: 'Plugin API', + link: '/dev-guide/plugin-api' + }, + { + text: 'Generator API', + link: '/dev-guide/generator-api' + }, + { + text: 'UI Plugin Info', + link: '/dev-guide/ui-info' + }, + { + text: 'UI Plugin API', + link: '/dev-guide/ui-api' + }, + { + text: 'UI Localization', + link: '/dev-guide/ui-localization' + }, + { + text: 'Discover More', + link: 'https://awesomejs.dev/for/vue-cli/' + } + ] + }, + { + text: 'Migrate from Older Versions', + items: [ + { + text: 'From Vue CLI v3 to v4', + link: '/migrations/migrate-from-v3' + }, + { + text: 'From Vue CLI v4 to v5', + link: '/migrations/migrate-from-v4' + }, + { + text: 'Full Changelog', + link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' + } + ] + } + ], + sidebar: { + '/guide/': [ + { + text: 'Overview', + link: '/guide/', + collapsable: true + }, + + { + text: 'Installation', + link: '/guide/installation' + }, + + { + text: 'Basics', + collapsable: false, + children: [ + { + text: 'Creating a Project', + link: '/guide/creating-a-project' + }, + { + text: 'Plugins and Presets', + link: '/guide/plugins-and-presets' + }, + { + text: 'CLI Service', + link: '/guide/cli-service' + } + ] + }, + + { + text: 'Development', + collapsable: false, + children: [ + { + text: 'Browser Compatibility', + link: '/guide/browser-compatibility' + }, + { + text: 'HTML and Static Assets', + link: '/guide/html-and-static-assets' + }, + { + text: 'Working with CSS', + link: '/guide/css' + }, + { + text: 'Working with Webpack', + link: '/guide/webpack' + }, + { + text: 'Modes and Environment Variables', + link: '/guide/mode-and-env' + }, + { + text: 'Build Targets', + link: '/guide/build-targets' + }, + { + text: 'Deployment', + link: '/guide/deployment' + }, + { + text: 'Troubleshooting', + link: '/guide/troubleshooting' + } + ] + } + ], + + '/dev-guide/': [ + { + text: 'Plugin Development Guide', + link: '/dev-guide/plugin-dev' + }, + { + text: 'API reference', + collapsable: false, + children: [ + { + text: 'Plugin API', + link: '/dev-guide/plugin-api' + }, + { + text: 'Generator API', + link: '/dev-guide/generator-api' + } + ] + }, + { + text: 'UI Development', + collapsable: false, + children: [ + { + text: 'UI Plugin Info', + link: '/dev-guide/ui-info' + }, + { + text: 'UI Plugin API', + link: '/dev-guide/ui-api' + }, + { + text: 'UI Localization', + link: '/dev-guide/ui-localization' + } + ] + } + ], + + '/core-plugins/': [ + { + text: 'Core Vue CLI Plugins', + collapsable: false, + children: [ + { + text: '@vue/cli-plugin-babel', + link: '/core-plugins/babel' + }, + { + text: '@vue/cli-plugin-typescript', + link: '/core-plugins/typescript' + }, + { + text: '@vue/cli-plugin-eslint', + link: '/core-plugins/eslint' + }, + { + text: '@vue/cli-plugin-pwa', + link: '/core-plugins/pwa' + }, + { + text: '@vue/cli-plugin-unit-jest', + link: '/core-plugins/unit-jest' + }, + { + text: '@vue/cli-plugin-unit-mocha', + link: '/core-plugins/unit-mocha' + }, + { + text: '@vue/cli-plugin-e2e-cypress', + link: '/core-plugins/e2e-cypress' + }, + { + text: '@vue/cli-plugin-e2e-nightwatch', + link: '/core-plugins/e2e-nightwatch' + }, + { + text: '@vue/cli-plugin-e2e-webdriverio', + link: '/core-plugins/e2e-webdriverio' + } + ] + } + ] + } + }, + '/zh/': { + label: '简体中文', + selectText: '选择语言', + lastUpdated: '上次编辑时间', + editLinkText: '在 GitHub 上编辑此页', + nav: [ + { + text: '指南', + link: '/zh/guide/' + }, + { + text: '配置参考', + link: '/zh/config/' + }, + { + text: '插件开发指南', + items: [ + { text: '插件开发指南', link: '/zh/dev-guide/plugin-dev' }, + { text: 'UI 插件信息', link: '/zh/dev-guide/ui-info' }, + { text: 'UI 插件 API', link: '/zh/dev-guide/ui-api' }, + { text: 'UI 本地化', link: '/zh/dev-guide/ui-localization' } + ] + }, + { + text: '插件', + items: [ + { + text: 'Babel', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-babel/README.md' + }, + { + text: 'TypeScript', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-typescript/README.md' + }, + { + text: 'ESLint', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-eslint/README.md' + }, + { + text: 'PWA', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-pwa/README.md' + }, + { + text: 'Jest', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-unit-jest/README.md' + }, + { + text: 'Mocha', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-unit-mocha/README.md' + }, + { + text: 'Cypress', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-e2e-cypress/README.md' + }, + { + text: 'Nightwatch', + link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-e2e-nightwatch/README.md' + } + ] + }, + { + text: '更新记录', + link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' + } + ], + sidebar: { + '/zh/guide/': [ + { + text: '介绍', + link: '/zh/guide/', + collapsable: true + }, + { + text: '安装', + link: '/zh/guide/installation' + }, + { + text: '基础', + collapsable: false, + children: [ + { + text: '创建一个项目', + link: '/zh/guide/creating-a-project' + }, + { + text: '插件和 Preset', + link: '/zh/guide/plugins-and-presets' + }, + { + text: 'CLI 服务', + link: '/zh/guide/cli-service' + } + ] + }, + { + text: '开发', + collapsable: false, + children: [ + { + text: '浏览器兼容性', + link: '/zh/guide/browser-compatibility' + }, + { + text: 'HTML 和静态资源', + link: '/zh/guide/html-and-static-assets' + }, + { + text: 'CSS 相关', + link: '/zh/guide/css' + }, + { + text: 'webpack 相关', + link: '/zh/guide/webpack' + }, + { + text: '模式和环境变量', + link: '/zh/guide/mode-and-env' + }, + { + text: '构建目标', + link: '/zh/guide/build-targets' + }, + { + text: '部署', + link: '/zh/guide/deployment' + } + ] + } + ], + '/zh/dev-guide/': [ + { + text: '插件开发指南', + link: '/zh/dev-guide/plugin-dev' + }, + { + title: 'UI 开发', + collapsable: false, + children: [ + { + text: 'UI 插件信息', + link: '/zh/dev-guide/ui-info' + }, + { + text: 'UI 插件 API', + link: '/zh/dev-guide/ui-api' + }, + { + text: 'UI 本地化', + link: '/zh/dev-guide/ui-localization' + } + ] + } + ] + } + }, + '/ru/': { + label: 'Русский', + selectText: 'Переводы', + lastUpdated: 'Последнее обновление', + editLinkText: 'Изменить эту страницу на GitHub', + nav: [ + { + text: 'Руководство', + link: '/ru/guide/' + }, + { + text: 'Конфигурация', + link: '/ru/config/' + }, + { + text: 'Плагины', + items: [ + { text: 'Основные плагины', link: '/ru/core-plugins/' }, + { + text: 'Руководство по разработке', + link: '/ru/dev-guide/plugin-dev' + }, + { text: 'API плагина', link: '/ru/dev-guide/plugin-api' }, + { text: 'API генератора', link: '/ru/dev-guide/generator-api' }, + { + text: 'Информация о плагине в UI', + link: '/ru/dev-guide/ui-info' + }, + { text: 'API плагина для UI', link: '/ru/dev-guide/ui-api' }, + { + text: 'Локализация в UI', + link: '/ru/dev-guide/ui-localization' + }, + { text: 'Поиск', link: 'https://awesomejs.dev/for/vue-cli/' } + ] + }, + { + text: 'Миграция с v3', + link: '/ru/migrating-from-v3/' + }, + { + text: 'История изменений', + link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' + } + ], + sidebar: { + '/ru/guide/': [ + { + text: 'Введение', + link: '/ru/guide/', + collapsable: true + }, + { + text: 'Установка', + link: '/ru/guide/installation' + }, + { + text: 'Основы', + collapsable: false, + children: [ + { + text: 'Создание проекта', + link: '/ru/guide/creating-a-project' + }, + { + text: 'Плагины и пресеты настроек', + link: '/ru/guide/creating-a-project' + }, + { + text: 'Сервис CLI', + link: '/ru/guide/cli-service' + } + ] + }, + { + text: 'Разработка', + collapsable: false, + children: [ + { + text: 'Совместимость с браузерами', + link: '/ru/guide/browser-compatibility' + }, + { + text: 'HTML и статические ресурсы', + link: '/ru/guide/html-and-static-assets' + }, + { + text: 'Работа с CSS', + link: '/ru/guide/css' + }, + { + text: 'Работа с Webpack', + link: '/ru/guide/webpack' + }, + { + text: 'Режимы работы и переменные окружения', + link: '/ru/guide/mode-and-env' + }, + { + text: 'Цели сборки', + link: '/ru/guide/build-targets' + }, + { + text: 'Публикация', + link: '/ru/guide/deployment' + }, + { + text: 'Поиск и устранение неисправностей', + link: '/ru/guide/troubleshooting' + } + ] + } + ], + '/ru/dev-guide/': [ + { + text: 'Руководство по разработке плагинов', + link: '/ru/dev-guide/plugin-dev' + }, + { + text: 'Справочник API', + collapsable: false, + children: [ + { + text: 'API плагина', + link: '/ru/dev-guide/plugin-api' + }, + { + text: 'API генератора', + link: '/ru/dev-guide/generator-api' + } + ] + }, + { + text: 'Разработка UI', + collapsable: false, + children: [ + { + text: 'Информация о плагине в UI', + link: '/ru/dev-guide/ui-info' + }, + { + text: 'API плагина для UI', + link: '/ru/dev-guide/ui-api' + }, + { + text: 'Локализация в UI', + link: '/ru/dev-guide/ui-localization' + } + ] + } + ], + '/ru/core-plugins/': [ + { + text: 'Основные плагины Vue CLI', + collapsable: false, + children: [ + { + text: '@vue/cli-plugin-babel', + link: '/ru/core-plugins/babel' + }, + { + text: '@vue/cli-plugin-typescript', + link: '/ru/core-plugins/typescript' + }, + { + text: '@vue/cli-plugin-eslint', + link: '/ru/core-plugins/eslint' + }, + { + text: '@vue/cli-plugin-pwa', + link: '/ru/core-plugins/pwa' + }, + { + text: '@vue/cli-plugin-unit-jest', + link: '/ru/core-plugins/unit-jest' + }, + { + text: '@vue/cli-plugin-unit-mocha', + link: '/ru/core-plugins/unit-mocha' + }, + { + text: '@vue/cli-plugin-e2e-cypress', + link: '/ru/core-plugins/e2e-cypress' + }, + { + text: '@vue/cli-plugin-e2e-nightwatch', + link: '/ru/core-plugins/e2e-nightwatch' + }, + { + text: '@vue/cli-plugin-e2e-webdriverio', + link: '/ru/core-plugins/e2e-webdriverio' + } + ] + } + ] + } + } + } + }, +} diff --git a/docs/.vitepress/self-destroying-service-worker.js b/docs/.vitepress/self-destroying-service-worker.js new file mode 100644 index 0000000000..9cfd20cecd --- /dev/null +++ b/docs/.vitepress/self-destroying-service-worker.js @@ -0,0 +1,38 @@ +// https://github.com/NekR/self-destroying-sw + +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 Arthur Stolyar + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + self.addEventListener('install', function(e) { + self.skipWaiting(); +}); + +self.addEventListener('activate', function(e) { + self.registration.unregister() + .then(function() { + return self.clients.matchAll(); + }) + .then(function(clients) { + clients.forEach(client => client.navigate(client.url)) + }); +}); diff --git a/docs/.vitepress/theme/AlgoliaSearchBox.vue b/docs/.vitepress/theme/AlgoliaSearchBox.vue new file mode 100644 index 0000000000..ec546c9fd7 --- /dev/null +++ b/docs/.vitepress/theme/AlgoliaSearchBox.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 0000000000..e4f16b3e30 --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,3 @@ +.home .home-hero img { + max-height: 180px; +} diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 0000000000..86aa96a8c6 --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1,93 @@ +import { h } from 'vue' +import DefaultTheme from 'vitepress/dist/client/theme-default' +import AlgoliaSearchBox from './AlgoliaSearchBox.vue' +import './custom.css' +import { useData } from 'vitepress' + +export default { + ...DefaultTheme, + Layout: { + setup() { + const { lang } = useData() + return () => { + return h(DefaultTheme.Layout, null, { + 'page-top': () => { + return lang.value === 'zh-CN' ? notice_zh_cn() : notice_en() + }, + 'navbar-search': () => { + return h(AlgoliaSearchBox, { + options: { + indexName: 'cli_vuejs', + apiKey: 'f6df220f7d246aff64a56300b7f19f21' + } + }) + } + }) + } + } + } +} + +function notice_en() { + return h('div', { class: 'warning custom-block' }, [ + h( + 'p', + { class: 'custom-block-title' }, + '⚠️ Vue CLI is in Maintenance Mode!' + ), + h('p', [ + 'For new projects, it is now recommended to use ', + h( + 'a', + { + href: 'https://github.com/vuejs/create-vue', + target: '_blank' + }, + [h('code', 'create-vue')] + ), + ' to scaffold ', + h('a', { href: 'https://vitejs.dev', target: '_blank' }, 'Vite'), + '-based projects. ', + 'Also refer to the ', + h( + 'a', + { + href: 'https://vuejs.org/guide/scaling-up/tooling.html', + target: '_blank' + }, + 'Vue 3 Tooling Guide' + ), + ' for the latest recommendations.' + ]) + ]) +} + +function notice_zh_cn() { + return h('div', { class: 'warning custom-block' }, [ + h('p', { class: 'custom-block-title' }, '⚠️ Vue CLI 现已处于维护模式!'), + h('p', [ + '现在官方推荐使用 ', + h( + 'a', + { + href: 'https://github.com/vuejs/create-vue', + target: '_blank' + }, + [h('code', 'create-vue')] + ), + ' 来创建基于 ', + h('a', { href: 'https://cn.vitejs.dev', target: '_blank' }, 'Vite'), + ' 的新项目。 ', + '另外请参考 ', + h( + 'a', + { + href: 'https://cn.vuejs.org/guide/scaling-up/tooling.html', + target: '_blank' + }, + 'Vue 3 工具链指南' + ), + ' 以了解最新的工具推荐。' + ]) + ]) +} diff --git a/docs/.vitepress/theme/search.svg b/docs/.vitepress/theme/search.svg new file mode 100644 index 0000000000..a9bb4b2def --- /dev/null +++ b/docs/.vitepress/theme/search.svg @@ -0,0 +1,2 @@ + + diff --git a/docs/.vuepress/components/Bit.vue b/docs/.vuepress/components/Bit.vue deleted file mode 100644 index 77a55fd085..0000000000 --- a/docs/.vuepress/components/Bit.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index 6ec5954c9a..0000000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,327 +0,0 @@ -module.exports = { - locales: { - '/': { - lang: 'en-US', - title: 'Vue CLI', - description: '🛠️ Standard Tooling for Vue.js Development' - }, - '/zh/': { - lang: 'zh-CN', - title: 'Vue CLI', - description: '🛠️ Vue.js 开发的标准工具' - }, - '/ru/': { - lang: 'ru', - title: 'Vue CLI', - description: '🛠️ Стандартный инструментарий для разработки на Vue.js' - } - }, - head: [ - ['link', { rel: 'icon', href: '/favicon.png' }], - ['link', { rel: 'manifest', href: '/manifest.json' }], - ['meta', { name: 'theme-color', content: '#3eaf7c' }], - ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], - ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }], - ['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }], - ['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }], - ['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }], - ['meta', { name: 'msapplication-TileColor', content: '#000000' }] - ], - plugins: { - '@vuepress/pwa': { - serviceWorker: true, - updatePopup: { - '/': { - message: "New content is available.", - buttonText: "Refresh" - }, - '/zh/': { - message: "发现新内容可用", - buttonText: "刷新" - }, - '/ru/': { - message: 'Доступно обновление контента', - buttonText: 'Обновить' - } - } - } - }, - theme: '@vuepress/theme-vue', - themeConfig: { - repo: 'vuejs/vue-cli', - docsDir: 'docs', - docsBranch: 'next', - editLinks: true, - sidebarDepth: 3, - algolia: { - indexName: 'cli_vuejs', - apiKey: 'f6df220f7d246aff64a56300b7f19f21', - }, - locales: { - '/': { - label: 'English', - selectText: 'Languages', - lastUpdated: 'Last Updated', - editLinkText: 'Edit this page on GitHub', - nav: [ - { - text: 'Guide', - link: '/guide/' - }, - { - text: 'Config Reference', - link: '/config/' - }, - { - text: 'Plugin Dev Guide', - items: [ - { text: 'Plugin Dev Guide', link: '/dev-guide/plugin-dev.md' }, - { text: 'UI Plugin Info', link: '/dev-guide/ui-info.md' }, - { text: 'UI Plugin API', link: '/dev-guide/ui-api.md' }, - { text: 'UI Localization', link: '/dev-guide/ui-localization.md' } - ] - }, - { - text: 'Plugins', - link: '/core-plugins/' - }, - { - text: 'Changelog', - link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' - } - ], - sidebar: { - '/guide/': [ - '/guide/', - '/guide/installation', - { - title: 'Basics', - collapsable: false, - children: [ - '/guide/prototyping', - '/guide/creating-a-project', - '/guide/plugins-and-presets', - '/guide/cli-service' - ] - }, - { - title: 'Development', - collapsable: false, - children: [ - '/guide/browser-compatibility', - '/guide/html-and-static-assets', - '/guide/css', - '/guide/webpack', - '/guide/mode-and-env', - '/guide/build-targets', - '/guide/deployment', - '/guide/troubleshooting' - ] - } - ], - '/dev-guide/': [ - '/dev-guide/plugin-dev.md', - { - title: 'API reference', - collapsable: false, - children: [ - '/dev-guide/plugin-api.md', - '/dev-guide/generator-api.md', - ] - }, - { - title: 'UI Development', - collapsable: false, - children: [ - '/dev-guide/ui-info.md', - '/dev-guide/ui-api.md', - '/dev-guide/ui-localization.md' - ] - } - ], - '/core-plugins/': [{ - title: 'Core Vue CLI Plugins', - collapsable: false, - children: [ - '/core-plugins/babel.md', - '/core-plugins/typescript.md', - '/core-plugins/eslint.md', - '/core-plugins/pwa.md', - '/core-plugins/unit-jest.md', - '/core-plugins/unit-mocha.md', - '/core-plugins/e2e-cypress.md', - '/core-plugins/e2e-nightwatch.md' - ] - }], - - } - }, - '/zh/': { - label: '简体中文', - selectText: '选择语言', - lastUpdated: '上次编辑时间', - editLinkText: '在 GitHub 上编辑此页', - nav: [ - { - text: '指南', - link: '/zh/guide/' - }, - { - text: '配置参考', - link: '/zh/config/' - }, - { - text: '插件开发指南', - items: [ - { text: '插件开发指南', link: '/zh/dev-guide/plugin-dev.md' }, - { text: 'UI 插件信息', link: '/zh/dev-guide/ui-info.md' }, - { text: 'UI 插件 API', link: '/zh/dev-guide/ui-api.md' }, - { text: 'UI 本地化', link: '/zh/dev-guide/ui-localization.md' } - ] - }, - { - text: '插件', - items: [ - { text: 'Babel', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-babel/README.md' }, - { text: 'TypeScript', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-typescript/README.md' }, - { text: 'ESLint', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-eslint/README.md' }, - { text: 'PWA', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-pwa/README.md' }, - { text: 'Jest', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-unit-jest/README.md' }, - { text: 'Mocha', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-unit-mocha/README.md' }, - { text: 'Cypress', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-e2e-cypress/README.md' }, - { text: 'Nightwatch', link: 'https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-e2e-nightwatch/README.md' } - ] - }, - { - text: '更新记录', - link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' - } - ], - sidebar: { - '/zh/guide/': [ - '/zh/guide/', - '/zh/guide/installation', - { - title: '基础', - collapsable: false, - children: [ - '/zh/guide/prototyping', - '/zh/guide/creating-a-project', - '/zh/guide/plugins-and-presets', - '/zh/guide/cli-service' - ] - }, - { - title: '开发', - collapsable: false, - children: [ - '/zh/guide/browser-compatibility', - '/zh/guide/html-and-static-assets', - '/zh/guide/css', - '/zh/guide/webpack', - '/zh/guide/mode-and-env', - '/zh/guide/build-targets', - '/zh/guide/deployment' - ] - } - ], - '/zh/dev-guide/': [ - '/zh/dev-guide/plugin-dev.md', - { - title: 'UI 开发', - collapsable: false, - children: [ - '/zh/dev-guide/ui-info.md', - '/zh/dev-guide/ui-api.md', - '/zh/dev-guide/ui-localization.md' - ] - } - ] - } - }, - '/ru/': { - label: 'Русский', - selectText: 'Переводы', - lastUpdated: 'Последнее обновление', - editLinkText: 'Изменить эту страницу на GitHub', - nav: [ - { - text: 'Руководство', - link: '/ru/guide/' - }, - { - text: 'Конфигурация', - link: '/ru/config/' - }, - { - text: 'Создание плагинов', - items: [ - { text: 'Руководство по разработке', link: '/ru/dev-guide/plugin-dev.md' }, - { text: 'Информация о плагине в UI', link: '/ru/dev-guide/ui-info.md' }, - { text: 'API плагина в UI', link: '/ru/dev-guide/ui-api.md' }, - { text: 'Локализация в UI', link: '/ru/dev-guide/ui-localization.md' } - ] - }, - { - text: 'Плагины', - items: [ - { text: 'Babel', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel' }, - { text: 'TypeScript', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript' }, - { text: 'ESLint', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint' }, - { text: 'PWA', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa' }, - { text: 'Jest', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest' }, - { text: 'Mocha', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha' }, - { text: 'Cypress', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-cypress' }, - { text: 'Nightwatch', link: 'https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-nightwatch' } - ] - }, - { - text: 'История изменений', - link: 'https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md' - } - ], - sidebar: { - '/ru/guide/': [ - '/ru/guide/', - '/ru/guide/installation', - { - title: 'Основы', - collapsable: false, - children: [ - '/ru/guide/prototyping', - '/ru/guide/creating-a-project', - '/ru/guide/plugins-and-presets', - '/ru/guide/cli-service' - ] - }, - { - title: 'Разработка', - collapsable: false, - children: [ - '/ru/guide/browser-compatibility', - '/ru/guide/html-and-static-assets', - '/ru/guide/css', - '/ru/guide/webpack', - '/ru/guide/mode-and-env', - '/ru/guide/build-targets', - '/ru/guide/deployment' - ] - } - ], - '/ru/dev-guide/': [ - '/ru/dev-guide/plugin-dev.md', - { - title: 'Разработка UI', - collapsable: false, - children: [ - '/ru/dev-guide/ui-info.md', - '/ru/dev-guide/ui-api.md', - '/ru/dev-guide/ui-localization.md' - ] - } - ] - } - } - } - } -} diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl deleted file mode 100644 index f272762b9c..0000000000 --- a/docs/.vuepress/styles/index.styl +++ /dev/null @@ -1,8 +0,0 @@ -.home .hero img - max-height 180px - -.search-box .suggestion a - white-space normal - -#carbonads a - display inline !important diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 9d6c370f4d..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -home: true -heroImage: /favicon.png -actionText: Get Started → -actionLink: /guide/ -footer: MIT Licensed | Copyright © 2018-present Evan You ---- - -
- -
- -
-
-

Feature Rich

-

Out-of-the-box support for Babel, TypeScript, ESLint, PostCSS, PWA, Unit Testing & End-to-end Testing.

-
-
-

Extensible

-

The plugin system allows the community to build and share reusable solutions to common needs.

-
-
-

No Need to Eject

-

Vue CLI is fully configurable without the need for ejecting. This allows your project to stay up-to-date for the long run.

-
-
-

Graphical User Interface

-

Create, develop and manage your projects through an accompanying graphical user interface.

-
-
-

Instant Prototyping

-

Instantly prototype new ideas with a single Vue file.

-
-
-

Future Ready

-

Effortlessly ship native ES2015 code for modern browsers, or build your vue components as native web components.

-
-
- -## Getting Started - -Install: - -``` bash -npm install -g @vue/cli -# OR -yarn global add @vue/cli -``` - -Create a project: - -``` bash -vue create my-project -# OR -vue ui -``` diff --git a/docs/config/README.md b/docs/config/index.md similarity index 86% rename from docs/config/README.md rename to docs/config/index.md index 2c6200c0c5..8fb0a57f45 100644 --- a/docs/config/README.md +++ b/docs/config/index.md @@ -4,8 +4,6 @@ sidebar: auto # Configuration Reference - - ## Global CLI Config Some global configurations for `@vue/cli`, such as your preferred package manager and your locally saved presets, are stored in a JSON file named `.vuerc` in your home directory. You can edit this file directly with your editor of choice to change the saved options. @@ -24,11 +22,26 @@ The file should export an object containing options: ``` js // vue.config.js + +/** + * @type {import('@vue/cli-service').ProjectOptions} + */ module.exports = { // options... } ``` +Or, you can use the `defineConfig` helper from `@vue/cli-service`, which could provide better intellisense support: + +```js +// vue.config.js +const { defineConfig } = require('@vue/cli-service') + +module.exports = defineConfig({ + // options... +}) +``` + ### baseUrl Deprecated since Vue CLI 3.3, please use [`publicPath`](#publicPath) instead. @@ -67,7 +80,7 @@ Deprecated since Vue CLI 3.3, please use [`publicPath`](#publicPath) instead. - Type: `string` - Default: `'dist'` - The directory where the production build files will be generated in when running `vue-cli-service build`. Note the target directory will be removed before building (this behavior can be disabled by passing `--no-clean` when building). + The directory where the production build files will be generated in when running `vue-cli-service build`. Note the target directory contents will be removed before building (this behavior can be disabled by passing `--no-clean` when building). ::: tip Always use `outputDir` instead of modifying webpack `output.path`. @@ -141,7 +154,7 @@ Deprecated since Vue CLI 3.3, please use [`publicPath`](#publicPath) instead. ### lintOnSave - Type: `boolean | 'warning' | 'default' | 'error'` -- Default: `true` +- Default: `'default'` Whether to perform lint-on-save during development using [eslint-loader](https://github.com/webpack-contrib/eslint-loader). This value is respected only when [`@vue/cli-plugin-eslint`](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint) is installed. @@ -149,7 +162,7 @@ Deprecated since Vue CLI 3.3, please use [`publicPath`](#publicPath) instead. To make lint errors show up in the browser overlay, you can use `lintOnSave: 'default'`. This will force `eslint-loader` to actually emit errors. this also means lint errors will now cause the compilation to fail. - Setting it to `'errors'` will force eslint-loader to emit warnings as errors as well, which means warnings will also show up in the overlay. + Setting it to `'error'` will force eslint-loader to emit warnings as errors as well, which means warnings will also show up in the overlay. Alternatively, you can configure the overlay to display both warnings and errors: @@ -185,10 +198,12 @@ Deprecated since Vue CLI 3.3, please use [`publicPath`](#publicPath) instead. ### transpileDependencies -- Type: `Array` -- Default: `[]` +- Type: `boolean | Array` +- Default: `false` + + By default `babel-loader` ignores all files inside `node_modules`. You can enable this option to avoid unexpected untranspiled code from third-party dependencies. - By default `babel-loader` ignores all files inside `node_modules`. If you want to explicitly transpile a dependency with Babel, you can list it in this option. + Transpiling all the dependencies could slow down the build process, though. If build performance is a concern, you can explicitly transpile only some of the dependencies by passing an array of package names or name patterns to this option. ::: warning Jest config This option is not respected by the [cli-unit-jest plugin](#jest), because in jest, we don't have to transpile code from `/node_modules` unless it uses non-standard features - Node >8.11 supports the latest ECMAScript features already. @@ -247,29 +262,16 @@ See [the plugin's README](https://github.com/vuejs/vue-cli/blob/dev/packages/%40 ### css.modules -Deprecated since v4, please use [`css.requireModuleExtension`](#css-requireModuleExtension) instead. - -In v3 this means the opposite of `css.requireModuleExtension`. - ### css.requireModuleExtension -- Type: `boolean` -- Default: `true` - - By default, only files that ends in `*.module.[ext]` are treated as CSS modules. Setting this to `false` will allow you to drop `.module` in the filenames and treat all `*.(css|scss|sass|less|styl(us)?)` files as CSS modules. - - ::: tip - If you have customized CSS Modules configurations in `css.loaderOptions.css`, then the `css.requireModuleExtension` field must be explictly configured to `true` or `false`, otherwise we can't be sure whether you want to apply these options to all CSS files or not. - ::: - - See also: [Working with CSS > CSS Modules](../guide/css.md#css-modules) +Both removed in v5, see [Working with CSS > CSS Modules](../guide/css.md#css-modules) for guidance on how to treat all style imports as CSS Modules. ### css.extract - Type: `boolean | Object` - Default: `true` in production, `false` in development - Whether to extract CSS in your components into a standalone CSS files (instead of inlined in JavaScript and injected dynamically). + Whether to extract CSS in your components into a standalone CSS file (instead of inlined in JavaScript and injected dynamically). This is always disabled when building as web components (styles are inlined and injected into shadowRoot). @@ -277,6 +279,8 @@ In v3 this means the opposite of `css.requireModuleExtension`. Extracting CSS is disabled by default in development mode since it is incompatible with CSS hot reloading. However, you can still enforce extraction in all cases by explicitly setting the value to `true`. + Instead of a `true`, you can also pass an object of options for the [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) if you want to further configure what this plugin does exactly. + ### css.sourceMap - Type: `boolean` @@ -330,7 +334,7 @@ In v3 this means the opposite of `css.requireModuleExtension`. - Some values like `host`, `port` and `https` may be overwritten by command line flags. - - Some values like `publicPath` and `historyApiFallback` should not be modified as they need to be synchronized with [publicPath](#baseurl) for the dev server to function properly. + - Some values like `publicPath` and `historyApiFallback` should not be modified as they need to be synchronized with [publicPath](#publicPath) for the dev server to function properly. ### devServer.proxy @@ -373,6 +377,16 @@ In v3 this means the opposite of `css.requireModuleExtension`. } ``` +### devServer.inline + +- Type: `boolean` +- Default: `true` + + Toggle between the dev-server's two different modes. See [devServer.inline](https://webpack.js.org/configuration/dev-server/#devserverinline) for more details. Note that: + + - To use the `iframe mode` no additional configuration is needed. Just navigate the browser to `http://:/webpack-dev-server/` to debug your app. A notification bar with messages will appear at the top of your app. + - To use the `inline mode`, just navigate to `http://:/` to debug your app. The build messages will appear in the browser console. + ### parallel - Type: `boolean | number` @@ -380,6 +394,10 @@ In v3 this means the opposite of `css.requireModuleExtension`. Whether to use `thread-loader` for Babel or TypeScript transpilation. This is enabled for production builds when the system has more than 1 CPU cores. Passing a number will define the amount of workers used. +::: warning +Do not use `parallel` in combination with non-serializable loader options, such as regexes, dates and functions. These options would not be passed correctly to the respective loaders which may lead to unexpected errors. +::: + ### pwa - Type: `Object` @@ -446,3 +464,7 @@ See [@vue/cli-plugin-e2e-cypress](https://github.com/vuejs/vue-cli/tree/dev/pack ### Nightwatch See [@vue/cli-plugin-e2e-nightwatch](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-nightwatch) for more details. + +### WebdriverIO + +See [@vue/cli-plugin-e2e-webdriverio](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-webdriverio) for more details. diff --git a/docs/core-plugins/babel.md b/docs/core-plugins/babel.md index 278d077509..2dbb2b6251 100644 --- a/docs/core-plugins/babel.md +++ b/docs/core-plugins/babel.md @@ -20,15 +20,17 @@ module.exports = { ## Caching -[cache-loader](https://github.com/webpack-contrib/cache-loader) is enabled by default and cache is stored in `/node_modules/.cache/babel-loader`. +Cache options of [babel-loader](https://github.com/babel/babel-loader#options) is enabled by default and cache is stored in `/node_modules/.cache/babel-loader`. ## Parallelization [thread-loader](https://github.com/webpack-contrib/thread-loader) is enabled by default when the machine has more than 1 CPU cores. This can be turned off by setting `parallel: false` in `vue.config.js`. +`parallel` should be set to `false` when using Babel in combination with non-serializable loader options, such as regexes, dates and functions. These options would not be passed correctly to `babel-loader` which may lead to unexpected errors. + ## Installing in an Already Created Project -``` sh +```bash vue add babel ``` @@ -36,4 +38,3 @@ vue add babel - `config.rule('js')` - `config.rule('js').use('babel-loader')` -- `config.rule('js').use('cache-loader')` diff --git a/docs/core-plugins/e2e-cypress.md b/docs/core-plugins/e2e-cypress.md index 4659a24528..fe661ce9df 100644 --- a/docs/core-plugins/e2e-cypress.md +++ b/docs/core-plugins/e2e-cypress.md @@ -4,7 +4,9 @@ This adds E2E testing support using [Cypress](https://www.cypress.io/). -Cypress offers a rich interactive interface for running E2E tests, but currently only supports running the tests in Chromium. If you have a hard requirement on E2E testing in multiple browsers, consider using the Selenium-based [@vue/cli-plugin-e2e-nightwatch](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-nightwatch). +Cypress offers a rich interactive interface for running E2E tests in Firefox and Chromium based browsers (Chrome, MS Edge, Brave, Electron). To learn more about cross browser testing, visit the [Cypress Cross Browser Testing Guide](https://on.cypress.io/cross-browser-testing). + +> **Note:** If you have a hard requirement on E2E testing in IE or Safari, consider using the Selenium-based [@vue/cli-plugin-e2e-nightwatch](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-nightwatch). ## Injected Commands @@ -12,7 +14,7 @@ Cypress offers a rich interactive interface for running E2E tests, but currently Run e2e tests with `cypress run`. - By default it launches Cypress in interactive mode with a GUI. If you want to run the tests in headless mode (e.g. for CI), you can do so with the `--headless` option. + By default it launches Cypress in interactive mode with a GUI (via `cypress open`). If you want to run the tests in headless mode (e.g. for CI), you can do so with the `--headless` option. The command automatically starts a server in production mode to run the e2e tests against. If you want to run the tests multiple times without having to restart the server every time, you can start the server with `vue-cli-service serve --mode production` in one terminal, and then run e2e tests against that server using the `--url` option. @@ -43,6 +45,6 @@ Cypress doesn't load .env files for your test files the same way as `vue-cli` do ## Installing in an Already Created Project -``` sh +```bash vue add e2e-cypress ``` diff --git a/docs/core-plugins/e2e-nightwatch.md b/docs/core-plugins/e2e-nightwatch.md index 4bc631fe58..e77d34ee53 100644 --- a/docs/core-plugins/e2e-nightwatch.md +++ b/docs/core-plugins/e2e-nightwatch.md @@ -21,13 +21,13 @@ -f, --filter glob to filter tests by filename ``` - Additionally, all [Nightwatch CLI options](https://nightwatchjs.org/guide/running-tests/#command-line-options) are also supported. + Additionally, all [Nightwatch CLI options](https://nightwatchjs.org/guide/running-tests/#command-line-options) are also supported. E.g.: `--verbose`, `--retries` etc. - + ## Project Structure -The following structure will be generated when installing this plugin. There are examples for most testing concepts in Nightwatch available. +The following structure will be generated when installing this plugin. There are examples for most testing concepts in Nightwatch available. ``` tests/e2e/ @@ -55,14 +55,14 @@ Files located here are loaded automatically by Nightwatch and placed onto the `. Files located here are loaded automatically by Nightwatch and placed onto the main `browser` api object to extend the built-in Nightwatch commands. See [writing custom commands](https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands) for details. #### `page objects` -Working with page objects is a popular methodology in end-to-end UI testing. Files placed in this folder are automatically loaded onto the `.page` api namespace, with the name of the file being the name of the page object. See [working with page objects](https://nightwatchjs.org/guide/working-with-page-objects/) section for details. +Working with page objects is a popular methodology in end-to-end UI testing. Files placed in this folder are automatically loaded onto the `.page` api namespace, with the name of the file being the name of the page object. See [working with page objects](https://nightwatchjs.org/guide/working-with-page-objects/) section for details. #### `globals.js` The external globals file which can hold global properties or hooks. See [test globals](https://nightwatchjs.org/gettingstarted/configuration/#test-globals) section. ## Installing in an Already Created Project -``` sh +```bash vue add e2e-nightwatch ``` @@ -78,7 +78,7 @@ Consult Nightwatch docs for [configuration options](https://nightwatchjs.org/get By default, all tests inside the `specs` folder will be run using Chrome. If you'd like to run end-to-end tests against Chrome (or Firefox) in headless mode, simply pass the `--headless` argument. -```sh +```bash $ vue-cli-service test:e2e ``` @@ -86,7 +86,7 @@ $ vue-cli-service test:e2e To run a single test supply the filename path. E.g.: -```sh +```bash $ vue-cli-service test:e2e tests/e2e/specs/test.js ``` @@ -94,7 +94,7 @@ $ vue-cli-service test:e2e tests/e2e/specs/test.js If the development server is already running and you want to skip starting it automatically, pass the `--url` argument: -```sh +```bash $ vue-cli-service test:e2e --url http://localhost:8080/ ``` @@ -102,7 +102,7 @@ $ vue-cli-service test:e2e --url http://localhost:8080/ Support for running tests in Firefox is also available by default. Simply run the following (optionally add `--headless` to run Firefox in headless mode): -```sh +```bash $ vue-cli-service test:e2e --env firefox [--headless] ``` @@ -110,34 +110,34 @@ $ vue-cli-service test:e2e --env firefox [--headless] You can also run the tests simultaneously in both browsers by supplying both test environments separated by a comma (",") - no spaces. -```sh +```bash $ vue-cli-service test:e2e --env firefox,chrome [--headless] ``` **Running Tests in Parallel** -For a significantly faster test run, you can enable parallel test running when there are several test suites. Concurrency is performed at the file level and is distributed automatically per available CPU core. +For a significantly faster test run, you can enable parallel test running when there are several test suites. Concurrency is performed at the file level and is distributed automatically per available CPU core. For now, this is only available in Chromedriver. Read more about [parallel running](https://nightwatchjs.org/guide/running-tests/#parallel-running) in the Nightwatch docs. -```sh +```bash $ vue-cli-service test:e2e --parallel ``` **Running with Selenium** -Since `v4`, the Selenium standalone server is not included anymore in this plugin and in most cases running with Selenium is not required since Nightwatch v1.0. +Since `v4`, the Selenium standalone server is not included anymore in this plugin and in most cases running with Selenium is not required since Nightwatch v1.0. It is still possible to use the Selenium server, by following these steps: __1.__ Install `selenium-server` NPM package: - ```sh + ```bash $ npm install selenium-server --save-dev ``` - + __2.__ Run with `--use-selenium` cli argument: - ```sh + ```bash $ vue-cli-service test:e2e --use-selenium ``` diff --git a/docs/core-plugins/e2e-webdriverio.md b/docs/core-plugins/e2e-webdriverio.md new file mode 100644 index 0000000000..bf75325e66 --- /dev/null +++ b/docs/core-plugins/e2e-webdriverio.md @@ -0,0 +1,81 @@ +# @vue/cli-plugin-e2e-webdriverio + +> e2e-webdriverio plugin for vue-cli + +## Injected Commands + +- **`vue-cli-service test:e2e`** + + Run end-to-end tests with [WebdriverIO](https://webdriver.io/). + + Options: + + ``` + --remote Run tests remotely on SauceLabs + + All WebdriverIO CLI options are also supported. + + ``` + + Additionally, all [WebdriverIO CLI options](https://webdriver.io/docs/clioptions.html) are also supported. + E.g.: `--baseUrl`, `--bail` etc. + + +## Project Structure + +The following structure will be generated when installing this plugin: + +``` +tests/e2e/ + ├── pageobjects/ + | └── app.page.js + ├── specs/ + | ├── app.spec.js + └── .eslintrc.js +``` + +In addition to that there will be 3 configuration files generated: + +- `wdio.shared.conf.js`: a shared configuration with all options defined for all environments +- `wdio.local.conf.js`: a local configuration for local testing +- `wdio.sauce.conf.js`: a remote configuration for testing on a cloud provider like [Sauce Labs](https://saucelabs.com/) + +The directories contain: + +#### `pageobjects` +Contains an example for an page object. Read more on using [PageObjects](https://webdriver.io/docs/pageobjects.html) with WebdriverIO. + +#### `specs` +Your e2e tests. + +## Installing in an Already Created Project + +```bash +vue add e2e-webdriverio +``` + +For users with older CLI versions you may need to run `vue add @vue/e2e-webdriverio`. + +## Running Tests + +By default, all tests inside the `specs` folder will be run using Chrome. If you'd like to run end-to-end tests against Chrome (or Firefox) in headless mode, simply pass the `--headless` argument. Tests will be automatically run in parallel when executed in the cloud. + +```bash +$ vue-cli-service test:e2e +``` + +**Running a single test** + +To run a single test supply the filename path. E.g.: + +```bash +$ vue-cli-service test:e2e --spec tests/e2e/specs/test.js +``` + +**Skip Dev server auto-start** + +If the development server is already running and you want to skip starting it automatically, pass the `--url` argument: + +```bash +$ vue-cli-service test:e2e --baseUrl=http://localhost:8080/ +``` diff --git a/docs/core-plugins/eslint.md b/docs/core-plugins/eslint.md index 804af750ec..3d1f55772e 100644 --- a/docs/core-plugins/eslint.md +++ b/docs/core-plugins/eslint.md @@ -11,19 +11,28 @@ Options: - --format [formatter] specify formatter (default: codeframe) + --format [formatter] specify formatter (default: stylish) --no-fix do not fix errors --max-errors specify number of errors to make build failed (default: 0) --max-warnings specify number of warnings to make build failed (default: Infinity) + --output-file specify file to write report to ``` - Lints and fixes files. If no specific files are given, it lints all files in `src` and `test`. +Lints and fixes files. If no specific files are given, it lints all files in `src` and `tests`, as well as all JavaScript files in the root directory (these are most often config files such as `babel.config.js` or `.eslintrc.js`). - Other [ESLint CLI options](https://eslint.org/docs/user-guide/command-line-interface#options) are also supported. +Other [ESLint CLI options](https://eslint.org/docs/user-guide/command-line-interface#options) are not supported. + +::: tip +`vue-cli-service lint` will lint dotfiles `.*.js` by default. If you want to follow ESLint's default behavior instead, consider adding a `.eslintignore` file in your project. +::: ## Configuration -ESLint can be configured via `.eslintrc` or the `eslintConfig` field in `package.json`. +ESLint can be configured via `.eslintrc` or the `eslintConfig` field in `package.json`. See the [ESLint configuration docs](https://eslint.org/docs/user-guide/configuring) for more detail. + +::: tip +The following option is under the section of [`vue.config.js`](https://cli.vuejs.org/config/#vue-config-js). It is respected only when `@vue/cli-plugin-eslint` is installed. +::: Lint-on-save during development with `eslint-loader` is enabled by default. It can be disabled with the `lintOnSave` option in `vue.config.js`: @@ -62,7 +71,7 @@ module.exports = { ## Installing in an Already Created Project -``` sh +```bash vue add eslint ``` diff --git a/docs/core-plugins/README.md b/docs/core-plugins/index.md similarity index 94% rename from docs/core-plugins/README.md rename to docs/core-plugins/index.md index 1d62026f55..65398ddd13 100644 --- a/docs/core-plugins/README.md +++ b/docs/core-plugins/index.md @@ -12,3 +12,4 @@ This section contains documentation for core Vue CLI plugins: - [Mocha](unit-mocha.md) - [Cypress](e2e-cypress.md) - [Nightwatch](e2e-nightwatch.md) +- [WebdriverIO](e2e-webdriverio.md) diff --git a/docs/core-plugins/pwa.md b/docs/core-plugins/pwa.md index abf24578b9..ed406900fd 100644 --- a/docs/core-plugins/pwa.md +++ b/docs/core-plugins/pwa.md @@ -15,7 +15,7 @@ file, or the `"vue"` field in `package.json`. - **pwa.workboxPluginMode** - This allows you to the choose between the two modes supported by the underlying + This allows you to choose between the two modes supported by the underlying [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin). - `'GenerateSW'` (default), will lead to a new service worker file being created @@ -69,7 +69,7 @@ file, or the `"vue"` field in `package.json`. - Default: `'manifest.json'` - The path of app’s manifest. + The path of app’s manifest. If the path is an URL, the plugin won't generate a manifest.json in the dist directory during the build. - **pwa.manifestOptions** @@ -84,12 +84,19 @@ file, or the `"vue"` field in `package.json`. - display: `'standalone'` - theme_color: `pwa.themeColor` +- **pwa.manifestCrossorigin** + + - Default: `undefined` + + Value for `crossorigin` attribute in manifest link tag in the generated HTML. You may need to set this if your PWA is behind an authenticated proxy. See [cross-origin values](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-crossorigin) for more details. + - **pwa.iconPaths** - Defaults: ```js { + faviconSVG: 'img/icons/favicon.svg', favicon32: 'img/icons/favicon-32x32.png', favicon16: 'img/icons/favicon-16x16.png', appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', @@ -98,7 +105,7 @@ file, or the `"vue"` field in `package.json`. } ``` - Change these values to use different paths for your icons. + Change these values to use different paths for your icons. As of v4.3.0, you can use `null` as a value and that icon will not be included. ### Example Configuration @@ -126,7 +133,7 @@ module.exports = { ## Installing in an Already Created Project -``` sh +```bash vue add pwa ``` diff --git a/docs/core-plugins/router.md b/docs/core-plugins/router.md index 7d79da9dab..48fb1af544 100644 --- a/docs/core-plugins/router.md +++ b/docs/core-plugins/router.md @@ -4,6 +4,6 @@ ## Installing in an Already Created Project -``` sh +```bash vue add router ``` diff --git a/docs/core-plugins/typescript.md b/docs/core-plugins/typescript.md index 7a588a55a8..a5462c5cd4 100644 --- a/docs/core-plugins/typescript.md +++ b/docs/core-plugins/typescript.md @@ -12,10 +12,6 @@ Since `3.0.0-rc.6`, `typescript` is now a peer dependency of this package, so yo This plugin can be used alongside `@vue/cli-plugin-babel`. When used with Babel, this plugin will output ES2015 and delegate the rest to Babel for auto polyfill based on browser targets. -## Injected Commands - -If opted to use [TSLint](https://palantir.github.io/tslint/) during project creation, `vue-cli-service lint` will be injected. - ## Caching [cache-loader](https://github.com/webpack-contrib/cache-loader) is enabled by default and cache is stored in `/node_modules/.cache/ts-loader`. @@ -24,9 +20,11 @@ If opted to use [TSLint](https://palantir.github.io/tslint/) during project crea [thread-loader](https://github.com/webpack-contrib/thread-loader) is enabled by default when the machine has more than 1 CPU cores. This can be turned off by setting `parallel: false` in `vue.config.js`. +`parallel` should be set to `false` when using Typescript in combination with non-serializable loader options, such as regexes, dates and functions. These options would not be passed correctly to `ts-loader` which may lead to unexpected errors. + ## Installing in an Already Created Project -``` sh +```bash vue add typescript ``` diff --git a/docs/core-plugins/unit-jest.md b/docs/core-plugins/unit-jest.md index d2944bc748..763750a440 100644 --- a/docs/core-plugins/unit-jest.md +++ b/docs/core-plugins/unit-jest.md @@ -21,12 +21,12 @@ Note that directly running `jest` will fail because the Babel preset requires hi If you want to debug your tests via the Node inspector, you can run the following: -```sh +```bash # macOS or linux -node --inspect-brk ./node_modules/.bin/vue-cli-service test:unit +node --inspect-brk ./node_modules/.bin/vue-cli-service test:unit --runInBand # Windows -node --inspect-brk ./node_modules/@vue/cli-service/bin/vue-cli-service.js test:unit +node --inspect-brk ./node_modules/@vue/cli-service/bin/vue-cli-service.js test:unit --runInBand ``` ## Configuration @@ -35,7 +35,7 @@ Jest can be configured via `jest.config.js` in your project root, or the `jest` ## Installing in an Already Created Project -```sh +```bash vue add unit-jest ``` diff --git a/docs/core-plugins/unit-mocha.md b/docs/core-plugins/unit-mocha.md index 3f824b344e..0f49d411d4 100644 --- a/docs/core-plugins/unit-mocha.md +++ b/docs/core-plugins/unit-mocha.md @@ -6,7 +6,7 @@ - **`vue-cli-service test:unit`** - Run unit tests with [mocha-webpack](https://github.com/zinserjan/mocha-webpack) + [chai](http://chaijs.com/). + Run unit tests with [mochapack](https://github.com/sysgears/mochapack) + [chai](http://chaijs.com/). **Note the tests are run inside Node.js with browser environment simulated with JSDOM.** @@ -27,10 +27,10 @@ Default files matches are: any files in `tests/unit` that end in `.spec.(ts|js)`. - All [mocha-webpack command line options](http://zinserjan.github.io/mocha-webpack/docs/installation/cli-usage.html) are also supported. + All [mochapack command line options](https://sysgears.github.io/mochapack/docs/installation/cli-usage.html) are also supported. ## Installing in an Already Created Project -``` sh +```bash vue add unit-mocha ``` diff --git a/docs/core-plugins/vuex.md b/docs/core-plugins/vuex.md index c8d4c36d58..d62a479192 100644 --- a/docs/core-plugins/vuex.md +++ b/docs/core-plugins/vuex.md @@ -4,6 +4,6 @@ ## Installing in an Already Created Project -``` sh +```bash vue add vuex ``` diff --git a/docs/dev-guide/generator-api.md b/docs/dev-guide/generator-api.md index 886cfb1d35..e66417e0d8 100644 --- a/docs/dev-guide/generator-api.md +++ b/docs/dev-guide/generator-api.md @@ -44,10 +44,10 @@ The version string for the **project local** `@vue/cli-service` version that is ## resolve - **Arguments** - - `{string} _path` - relative path from project root + - `{string} ..._paths` - A sequence of relative paths or path segments - **Returns** - - `{string}`- the resolved absolute path + - `{string}`- the resolved absolute path, caculated based on the current project root - **Usage**: Resolve a path for the current project diff --git a/docs/dev-guide/plugin-dev.md b/docs/dev-guide/plugin-dev.md index e064bf9d97..14aaf8f7cf 100644 --- a/docs/dev-guide/plugin-dev.md +++ b/docs/dev-guide/plugin-dev.md @@ -251,7 +251,7 @@ First, we need to read main file content with Node `fs` module (which provides a module.exports.hooks = (api) => { api.afterInvoke(() => { const fs = require('fs') - const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) const lines = contentMain.split(/\r?\n/g) }) } @@ -265,7 +265,7 @@ Then we should to find the string containing `render` word (it's usually a part module.exports.hooks = (api) => { api.afterInvoke(() => { const fs = require('fs') - const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) const lines = contentMain.split(/\r?\n/g) const renderIndex = lines.findIndex(line => line.match(/render/)) @@ -283,13 +283,13 @@ module.exports.hooks = (api) => { api.afterInvoke(() => { const { EOL } = require('os') const fs = require('fs') - const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) const lines = contentMain.split(/\r?\n/g) const renderIndex = lines.findIndex(line => line.match(/render/)) lines[renderIndex] += `${EOL} router,` - fs.writeFileSync(api.entryFile, lines.join(EOL), { encoding: 'utf-8' }) + fs.writeFileSync(api.resolve(api.entryFile), lines.join(EOL), { encoding: 'utf-8' }) }) } ``` @@ -489,7 +489,7 @@ The resolved answers object will be passed to the plugin's generator as options. Alternatively, the user can skip the prompts and directly initialize the plugin by passing options via the command line, e.g.: -``` bash +```bash vue invoke my-plugin --mode awesome ``` @@ -861,7 +861,7 @@ The logo should be a square non-transparent image (ideally 84x84). ## Publish Plugin to npm -To publish your plugin, you need to be registered an [npmjs.com](npmjs.com) and you should have `npm` installed globally. If it's your first npm module, please run +To publish your plugin, you need to be registered an [npmjs.com](https://www.npmjs.com) and you should have `npm` installed globally. If it's your first npm module, please run ```bash npm login diff --git a/docs/dev-guide/ui-api.md b/docs/dev-guide/ui-api.md index a742100d9e..1b78128bf0 100644 --- a/docs/dev-guide/ui-api.md +++ b/docs/dev-guide/ui-api.md @@ -146,7 +146,7 @@ See [Prompts](#prompts) for more info. The `data` object contains the JSON result of each config file content. -For example, let's say the user has the following `vue.config.js` in his project: +For example, let's say the user has the following `vue.config.js` in their project: ```js module.exports = { diff --git a/docs/dev-guide/ui-localization.md b/docs/dev-guide/ui-localization.md index 8ea036ce8e..295ae387b5 100644 --- a/docs/dev-guide/ui-localization.md +++ b/docs/dev-guide/ui-localization.md @@ -2,7 +2,7 @@ ## Translate the standard UI -To make collaboration and synchronisation easier, the English source locale from the `dev` branch is automatically imported to [Transifex](https://www.transifex.com/vuejs/vue-cli/dashboard/), a platform for collaborative translation. +To make collaboration and synchronization easier, the English source locale from the `dev` branch is automatically imported to [Transifex](https://www.transifex.com/vuejs/vue-cli/dashboard/), a platform for collaborative translation. For existing languages, you can [sign up as a translator](https://www.transifex.com/vuejs/vue-cli/dashboard/). For new languages, you can [request the new language](https://www.transifex.com/vuejs/vue-cli/dashboard/) after signing up. diff --git a/docs/guide/browser-compatibility.md b/docs/guide/browser-compatibility.md index 8942f8a5cd..2de757bf6b 100644 --- a/docs/guide/browser-compatibility.md +++ b/docs/guide/browser-compatibility.md @@ -18,7 +18,7 @@ If one of your dependencies need polyfills, you have a few options: 1. **If the dependency is written in an ES version that your target environments do not support:** Add that dependency to the [`transpileDependencies`](../config/#transpiledependencies) option in `vue.config.js`. This would enable both syntax transforms and usage-based polyfill detection for that dependency. -2. **If the dependency ships ES5 code and explicitly lists the polyfills needed:** you can pre-include the needed polyfills using the [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) option for `@vue/babel-preset-app`. **Note that `es6.promise` is included by default because it's very common for libs to depend on Promises.** +2. **If the dependency ships ES5 code and explicitly lists the polyfills needed:** you can pre-include the needed polyfills using the [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) option for `@vue/babel-preset-app`. **Note that `es.promise` is included by default because it's very common for libs to depend on Promises.** ``` js // babel.config.js @@ -26,8 +26,8 @@ If one of your dependencies need polyfills, you have a few options: presets: [ ['@vue/app', { polyfills: [ - 'es6.promise', - 'es6.symbol' + 'es.promise', + 'es.symbol' ] }] ] @@ -38,9 +38,9 @@ If one of your dependencies need polyfills, you have a few options: It's recommended to add polyfills this way instead of directly importing them in your source code, because polyfills listed here can be automatically excluded if your `browserslist` targets don't need them. ::: -3. **If the dependency ships ES5 code, but uses ES6+ features without explicitly listing polyfill requirements (e.g. Vuetify):** Use `useBuiltIns: 'entry'` and then add `import '@babel/polyfill'` to your entry file. This will import **ALL** polyfills based on your `browserslist` targets so that you don't need to worry about dependency polyfills anymore, but will likely increase your final bundle size with some unused polyfills. +3. **If the dependency ships ES5 code, but uses ES6+ features without explicitly listing polyfill requirements (e.g. Vuetify):** Use `useBuiltIns: 'entry'` and then add `import 'core-js/stable'; import 'regenerator-runtime/runtime';` to your entry file. This will import **ALL** polyfills based on your `browserslist` targets so that you don't need to worry about dependency polyfills anymore, but will likely increase your final bundle size with some unused polyfills. -See [@babel-preset/env docs](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage) for more details. +See [@babel/preset-env docs](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage) for more details. ### Polyfills when Building as Library or Web Components @@ -52,7 +52,7 @@ With Babel we are able to leverage all the newest language features in ES2015+, Vue CLI offers a "Modern Mode" to help you solve this problem. When building for production with the following command: -``` bash +```bash vue-cli-service build --modern ``` @@ -71,13 +71,6 @@ For a Hello World app, the modern bundle is already 16% smaller. In production, ::: tip ` +``` + +Также есть возможность заменять сразу несколько мест в файле одновременно, для этого потребуется обернуть заменяющие строки в блоки `<%# REPLACE %>` и `<%# END_REPLACE %>`: + +```ejs +--- +extend: '@vue/cli-service/generator/template/src/App.vue' +replace: + - !!js/regexp /Добро пожаловать в приложение Vue\.js/ + - !!js/regexp / +<%# END_REPLACE %> +``` + +### Ограничения по именованию файлов + +При необходимости создания шаблона файла, имя которого начинается с точки (например, `.env`), нужно следовать определённому соглашению об именовании, поскольку при публикации плагина в npm такие файлы игнорируются: + +```bash +# Шаблон файла должен использовать символ подчёркивания вместо точки: + +/generator/template/_env + +# При вызове api.render('./template') в каталоге проекта он будет сгенерирован как: - api.registerCommand('test', args => { - // регистрация команды `vue-cli-service test` +/generator/template/.env +``` + +Следовательно, также потребуется придерживаться определённого соглашения об именовании, если потребуется сгенерировать файл, имя которого начинается с подчёркивания: + +```bash +# Шаблоны таких файлов должны использовать 2 символа подчёркивания вместо одного: + +/generator/template/__variables.scss + +# При вызове api.render('./template') в каталоге проекта он будет сгенерирован как: + +/generator/template/_variables.scss +``` + + +### Расширение пакета + +Если нужно добавить новую зависимость в проект, создать npm-задачу или изменить `package.json` любым другим способом, можно использовать метод API `extendPackage`. + +```js +// generator/index.js + +module.exports = api => { + api.extendPackage({ + dependencies: { + 'vue-router-layout': '^0.1.2' + } }) } ``` -#### Установка режимов для команд +В примере выше добавляется одна зависимость: `vue-router-layout`. При вызове плагина этот npm-пакет будет установлен и зависимость добавлена в пользовательский файл `package.json`. -> Примечание: установка режимов плагинами была изменена в beta.10. +Этим же методом API можно добавлять npm-задачи в проект. Для этого нужно указать имя задачи и команду, которая будет выполняться, для добавления в секцию `scripts` файла `package.json`: -Если зарегистрированная в плагине команда должна запускаться в определённом режиме по умолчанию, -плагин должен предоставлять её через `module.exports.defaultModes` в формате `{ [commandName]: mode }`: +```js +// generator/index.js -``` js module.exports = api => { - api.registerCommand('build', () => { - // ... + api.extendPackage({ + scripts: { + greet: 'vue-cli-service greet' + } }) } +``` -module.exports.defaultModes = { - build: 'production' +В примере выше добавляется новая задача `greet`, которая будет запускать специальную команду сервиса vue-cli, создание которой подробнее описано в разделе [плагина для сервиса](#добавnение-новой-команды-в-cli-service). + +### Изменение основного файла + +С помощью методов генератора можно вносить изменения и в файлы проекта. Наиболее распространённым случаем является изменение основного файла `main.js` или `main.ts`: добавление новых импортов, вызовы новых `Vue.use()` и т.д. + +Рассмотрим случай, когда файл `router.js` создан с помощью [генерации новых шаблонов](#создание-новых-шабnонов) и теперь требуется импортировать этот маршрутизатор в основной файл. Для этого используем два метода API генератора: `entryFile` вернёт основной файл проекта (`main.js` или `main.ts`), а `injectImports` предоставит возможность добавить новые импорты в этот файл: + +```js +// generator/index.js + +api.injectImports(api.entryFile, `import router from './router'`) +``` + +Теперь, когда маршрутизатор импортирован, можно внедрить его в экземпляр Vue в основном файле. Используем для этого хук `afterInvoke`, который вызывается после записи файлов на диск. + +Сначала нужно прочитать содержимое основного файла с помощью модуля Node `fs` (который предоставляет API для взаимодействия с файловой системой) и разделить содержимое на строки: + +```js +// generator/index.js + +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const fs = require('fs') + const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) + }) } ``` -Это связано с тем, что ожидаемый режим для команды должен быть известен до загрузки переменных окружения, что в свою очередь должно произойти до загрузки пользовательских настроек / применения плагинов. +Затем находим строку, содержащую слово `render` (это обычно будет часть экземпляра Vue), и добавляем `router` в качестве следующей строки: -#### Получение итоговой конфигурации Webpack в плагинах +```js{9-10} +// generator/index.js -Плагин может получить итоговую конфигурацию webpack вызвав `api.resolveWebpackConfig()`. Каждый вызов генерирует новую конфигурацию webpack, которая может быть дополнительно изменена при необходимости: +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const fs = require('fs') + const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) -``` js -module.exports = api => { - api.registerCommand('my-build', args => { - const configA = api.resolveWebpackConfig() - const configB = api.resolveWebpackConfig() + const renderIndex = lines.findIndex(line => line.match(/render/)) + lines[renderIndex] += `\n router,` + }) +} +``` + +Наконец, сохраняем содержимое обратно в основной файл: + +```js{12-13} +// generator/index.js + +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const { EOL } = require('os') + const fs = require('fs') + const contentMain = fs.readFileSync(api.entryFile, { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) - // изменение configA и configB для разных целей... + const renderIndex = lines.findIndex(line => line.match(/render/)) + lines[renderIndex] += `${EOL} router,` + + fs.writeFileSync(api.resolve(api.entryFile), lines.join(EOL), { encoding: 'utf-8' }) }) } +``` -// не забудьте указать режим по умолчанию для правильных переменных окружения -module.exports.defaultModes = { - 'my-build': 'production' +## Плагин для сервиса + +Плагин для сервиса позволяет вносить изменения в конфигурацию webpack, создавать новые команды vue-cli или изменять существующие (такие как `serve` и `build`). + +Плагин для сервиса автоматически загружается при создании экземпляра сервиса — т.е. при каждом вызове команды `vue-cli-service` внутри проекта. Он располагается в файле `index.js` в корневом каталоге плагина CLI. + +Плагин для сервиса должен экспортировать функцию, которая принимает два аргумента: + +- Экземпляр [PluginAPI](/dev-guide/plugin-api.md) + +- Объект, содержащий локальные настройки проекта, указанные в файле `vue.config.js` или в поле `"vue"` файла `package.json`. + +Минимально необходимый код файла плагина для сервиса приведён ниже: + +```js +module.exports = () => {} +``` + +### Изменение конфигурации webpack + +API позволяет плагину для сервиса расширять/изменять внутреннюю конфигурацию webpack для различных окружений. Например, модифицируем конфигурацию webpack с помощью webpack-chain для добавления плагина `vue-auto-routing` с заданными параметрами: + +```js +const VueAutoRoutingPlugin = require('vue-auto-routing/lib/webpack-plugin') + +module.exports = (api, options) => { + api.chainWebpack(webpackConfig => { + webpackConfig + .plugin('vue-auto-routing') + .use(VueAutoRoutingPlugin, [ + { + pages: 'src/pages', + nested: true + } + ]) + }) } ``` -В качестве альтернативы, плагин также может получить новую [конфигурацию в формате chainable](https://github.com/mozilla-neutrino/webpack-chain) вызвав `api.resolveChainableWebpackConfig()`: +Также можно использовать метод `configureWebpack` для изменении конфигурации webpack или возврата объекта, который будет объединяться с конфигурацией с помощью webpack-merge. -``` js -api.registerCommand('my-build', args => { - const configA = api.resolveChainableWebpackConfig() - const configB = api.resolveChainableWebpackConfig() +### Добавление новой команды в cli-service - // изменяем цепочки configA и configB для разных целей... +С помощью плагина для сервиса можно зарегистрировать новую команду в cli-service в дополнение к стандартным (т.е. `serve` и `build`). Для этого можно использовать метод API `registerCommand`. - const finalConfigA = configA.toConfig() - const finalConfigB = configB.toConfig() -}) +Пример создания простой новой команды, которая выводит приветствие в консоли разработчика: + +```js +api.registerCommand( + 'greet', + { + description: 'Выводит приветствие в консоли', + usage: 'vue-cli-service greet' + }, + () => { + console.log(`👋 Привет`) + } +) ``` -#### Пользовательские настройки для сторонних плагинов +В этом примере мы задаём имя команды (`'greet'`), объект настроек с опциями `description` и `usage`, а также функцию, которая выполняется при запуске команды `vue-cli-service greet`. -Экспорт из `vue.config.js` [валидируется по схеме](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/options.js#L3) чтобы избежать опечаток и неправильных значений конфигурации. Тем не менее, можно настраивать поведение сторонних плагинов через поле `pluginOptions`. Например, для следующего `vue.config.js`: +:::tip Совет +Можно также добавить новую команду в список npm-задач проекта в файле `package.json` [с помощью генератора](#расширение-пакета). +::: -``` js -module.exports = { - pluginOptions: { - foo: { /* ... */ } +При запуске новой команды в проекте с установленным плагином появится сообщение в консоли: + +```bash +$ vue-cli-service greet +👋 Привет! +``` + +Можно указать список доступных опций для новой команды. Добавим опцию `--name` и изменим функцию для вывода этого имени, если оно было указано. + +```js +api.registerCommand( + 'greet', + { + description: 'Выводит приветствие в консоль', + usage: 'vue-cli-service greet [options]', + options: { '--name': 'определяет имя для приветствия' } + }, + args => { + if (args.name) { + console.log(`👋 Привет, ${args.name}!`); + } else { + console.log(`👋 Привет!`); + } } +); +``` + +Теперь при запуске команды `greet` с указанной опцией `--name`, это имя будет выводиться вместе с сообщением в консоли: + +```bash +$ vue-cli-service greet --name 'Джон' +👋 Привет, Джон! +``` + +### Изменение существующей команды в cli-service + +Если необходимо изменить существующую команду cli-service, сначала нужно получить её через `api.service.commands` и затем внести некоторые изменения. К примеру, выведем сообщение в консоли с номером порта, на котором запущено приложение: + +```js +const { serve } = api.service.commands + +const serveFn = serve.fn + +serve.fn = (...args) => { + return serveFn(...args).then(res => { + if (res && res.url) { + console.log(`Проект запущен по адресу ${res.url}`) + } + }) } ``` -Сторонний плагин может получить доступ к свойству `projectOptions.pluginOptions.foo` для определения собственной конфигурации. +В примере выше сначала получаем команду `serve` из списка существующих команд; затем изменяем её `fn`-часть (`fn` — это третий параметр, передаваемый при создании новой команды; он определяет функцию, запускаемую при выполнении команды). После внесения модификаций сообщение в консоли будет выводиться после успешного выполнения команды `serve`. + +### Определение режима работы команды + +Если команда, зарегистрированная плагином, должна запускаться в определённом режиме, плагин должен определять его через `module.exports.defaultModes` в виде `{ [commandName]: mode }`: + +```js +module.exports = api => { + api.registerCommand('build', () => { + // ... + }) +} -### Генератор (Generator) +module.exports.defaultModes = { + build: 'production' +} +``` -Плагин для CLI, опубликованный как пакет, может содержать файл `generator.js` или `generator/index.js`. Генератор внутри плагина вызывается в двух возможных сценариях: +Это связано с тем, что ожидаемый режим для работы команды должен быть известен до загрузки переменных окружения, что произойдёт перед загрузкой пользовательских настроек / применением плагинов. -- Во время первоначального создания проекта, если плагин для CLI установлен как часть пресета для создания проекта. +## Интерактивные подсказки -- Когда плагин устанавливается после создания проекта и вызывается через `vue invoke`. +Интерактивные подсказки предназначены для обработки пользовательского выбора при создании нового проекта или добавлении нового плагина в существующий проект. Вся логика интерактивных подсказок расположена в файле `prompts.js`. Сами подсказки реализованы с помощью пакета [inquirer](https://github.com/SBoudrias/Inquirer.js) под капотом. -[GeneratorAPI][generator-api] позволяет генератору внедрять дополнительные зависимости или поля в `package.json` и добавлять файлы в проект. +При инициализации плагина пользователем командой `vue invoke`, если плагин содержит `prompts.js` в своем корневом каталоге, он будет использован во время вызова. Файл должен экспортировать массив [вопросов](https://github.com/SBoudrias/Inquirer.js#question), которые затем будут обработаны Inquirer.js. -Генератор должен экспортировать функцию, которая принимает три аргумента: +Необходимо экспортировать сам массив вопросов или функцию, которая возвращает его. + +Например, экспорт непосредственно массива вопросов: +```js +// prompts.js -1. Экземпляр `GeneratorAPI`; +module.exports = [ + { + type: 'input', + name: 'locale', + message: 'Используемый язык для локализации проекта.', + validate: input => !!input, + default: 'en' + }, + // ... +] +``` -2. Настройки генератора для этого плагина. Они будут получены во время интерактивного выбора пользователем на этапе создания проекта, или загружаются из сохранённого пресета в `~/.vuerc`. Например, если сохранённый файл `~/.vuerc` выглядит так: +Или экспорт функции, которая возвращает массив вопросов: +```js +// prompts.js - ``` json +// в качестве аргумента функции передаётся `package.json` проекта +module.exports = pkg => { + const prompts = [ { - "presets" : { - "foo": { - "plugins": { - "@vue/cli-plugin-foo": { "option": "bar" } - } - } - } + type: 'input', + name: 'locale', + message: 'Используемый язык для локализации проекта.', + validate: input => !!input, + default: 'en' } - ``` + ] + + // динамическое добавление интерактивной подсказки + if ('@vue/cli-plugin-eslint' in (pkg.devDependencies || {})) { + prompts.push({ + type: 'confirm', + name: 'useESLintPluginVueI18n', + message: 'Использовать ESLint-плагин для Vue I18n?' + }) + } - И если пользователь создаёт проект с использованием пресета `foo`, тогда генератор `@vue/cli-plugin-foo` получит `{ option: 'bar' }` в качестве второго аргумента. + return prompts +} +``` - Для стороннего плагина эти параметры будут получены из интерактивного выбора пользователем или аргументов командной строки, когда выполняется команда `vue invoke` (см. [Интерактивные подсказки для сторонних плагинов](#интерактивные-подсказки-дnя-сторонних-пnагинов)). +Итоговый объект с ответами будет передаваться в генератор плагина в качестве настроек. -3. Весь пресет (`presets.foo`) будет передан в качестве третьего аргумента. +Кроме того, пользователь может пропустить этап в интерактивными подсказками и напрямую инициализировать плагин, передав опции через командную строку, например: -**Например:** +```bash +vue invoke my-plugin --mode awesome +``` -``` js -module.exports = (api, options, rootOptions) => { - // изменение полей package.json - api.extendPackage({ - scripts: { - test: 'vue-cli-service test' - } - }) +Интерактивные подсказки могут быть [различных типов](https://github.com/SBoudrias/Inquirer.js#prompt-types), но наиболее широко в CLI применяются `checkbox` и `confirm`. Добавим интерактивную подсказку `confirm` и используем её в генераторе плагина чтобы создавать по условию [новый файл из шаблона](#создание-новых-шабnонов). - // копирование и рендеринг всех файлов в ./template с помощью ejs - api.render('./template') +```js +// prompts.js - if (options.foo) { - // генерация файлов по условию +module.exports = [ + { + name: `addExampleRoutes`, + type: 'confirm', + message: 'Добавить примеры маршрутов?', + default: false } +] +``` + +При вызове плагина пользователю будет предложено ответить на вопрос о добавлении примеров маршрутов с ответом по умолчанию «Нет». + +![Пример интерактивных подсказок](/prompts-example.png) + +Если необходимо использовать результат выбора пользователя в генераторе, ответ будет доступен по имени интерактивной подсказки. Теперь можно модифицировать `generator/index.js` так: + +```js +if (options.addExampleRoutes) { + api.render('./template', { + ...options + }) } ``` -#### Шаблоны генератора +Шаблон будет генерироваться только если пользователь согласился создать примеры маршрутов. -Когда вы вызываете `api.render('./template')`, генератор будет рендерить файлы в `./template` (разрешённые относительно файла генератора) с помощью [EJS](https://github.com/mde/ejs). +## Локальная установка плагина -Кроме того, вы можете наследовать и заменять части существующего файла шаблона (даже из другого пакета) с помощью YAML front-matter: +При разработке плагина может потребоваться протестировать его и проверить локально как он работает на проекте с помощью Vue CLI. Можно использовать существующий проект или создать новый в целях тестирования: -``` ejs ---- -extend: '@vue/cli-service/generator/template/src/App.vue' -replace: !!js/regexp / ``` -Также возможно выполнять несколько замен в файле, хотя вам потребуется обернуть строки для замены в блоки из `<%# REPLACE %>` и `<%# END_REPLACE %>`: +### Отображение задачи в UI -``` ejs ---- -extend: '@vue/cli-service/generator/template/src/App.vue' -replace: - - !!js/regexp /Welcome to Your Vue\.js App/ - - !!js/regexp / -<%# END_REPLACE %> ``` -#### Ограничения имён файлов +Теперь в обзоре проекта Vue UI можно увидеть, что задача появилась на странице `Tasks`. Можно увидеть её название, предоставленное описание, иконку ссылки, которая ведёт на указанный URL, а также экран для отображения результатов выполнения задачи: + +![Задача Greet в UI](/ui-greet-task.png) + +### Отображение экрана конфигурации + +Иногда в проекте могут быть пользовательские файлы конфигураций для различных функций или библиотек. С помощью плагина Vue CLI можно отображать конфигурацию в Vue UI, изменять её и сохранять (сохранение изменит соответствующий конфигурационный файл в проекте). По умолчанию в проекте Vue CLI имеется только главный экран конфигурации с настройками из `vue.config.js`. Если добавить ESLint в проект, то появится также экран конфигурации ESLint: + +![Экран конфигурации в UI](/ui-configuration-default.png) + +Давайте создадим экран конфигурации для плагина. Прежде всего, после добавления плагина в существующий проект, должен быть файл с пользовательской конфигурацией. Это означает, что требуется добавить этот файл в каталог `template` для шага [создания новых шаблонов](#создание-новых-шабnонов). + +По умолчанию пользовательский интерфейс конфигурации может читать и записывать файлы следующих форматов: `json`, `yaml`, `js`, `package`. Назовём новый файл `myConfig.js` и поместим его в корне каталога `template`: -Если вы хотите отрендерить файл шаблона, имя которого начинается с точки (т.е. `.env`) вам необходимо следовать определённому соглашению по именованию, поскольку файлы именуемые с точки (dotfiles) игнорируются при публикации вашего плагина в npm: ``` -# dotfile шаблоны должны использовать символ подчёркивания вместо точки: +. +└── generator + ├── index.js + └── template + ├── myConfig.js + └── src + ├── layouts + ├── pages + └── router.js +``` -/generator/template/_env +Теперь необходимо добавить в этот файл какую-то актуальную конфигурацию: -# При вызове api.render('./template'), это будет отрендерено в каталоге проекта как: +```js +// myConfig.js -.env +module.exports = { + color: 'black' +} ``` -Следовательно, это значит, что необходимо также следовать специальному соглашению по именованию если вы хотите отрендерить файл, чьё имя начинается с подчёркивания: + +После вызова плагина файл `myConfig.js` будет сгенерирован в корневом каталоге проекта. Теперь добавим новый экран конфигурации с помощью метода `api.describeConfig` в файле `ui.js`: + +Сначала нужно передать некоторую информацию: + +```js +// ui.js + +api.describeConfig({ + // Уникальный ID для конфигурации + id: 'org.ktsn.vue-auto-routing.config', + // Отображаемое имя + name: 'Настройка приветствия', + // Описание, отображаемое под именем + description: 'Можно настроить цвет текста приветствия', + // Ссылка «More info» + link: 'https://github.com/ktsn/vue-cli-plugin-auto-routing#readme' +}) ``` -# такие шаблоны должны иметь два символа подчёркивания вместо точки: -/generator/template/__variables.scss +:::danger Предупреждение +Убедитесь в точности пространства имён для id, так как он должен быть уникальным для всех плагинов. Рекомендуем использовать [обратную нотацию записи доменного имени](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) +::: + +#### Логотип конфигурации + +Можно выбрать значок для конфигурации. Это может быть код [значка Material](https://material.io/tools/icons/?style=baseline) или пользовательское изображение (см. [публичные статические файлы](ui-api.md#пубnичные-статические-файnы)). -# При вызове api.render('./template'), это будет отрендерено в каталоге проекта как: +```js +// ui.js -_variables.scss +api.describeConfig({ + /* ... */ + // Значок конфигурации + icon: 'color_lens' +}) ``` -### Интерактивные подсказки +Если значок не указан, то будет использоваться логотип плагина, если таковой есть (см. [Логотип](#логотип)). -#### Интерактивные подсказки для встроенных плагинов +#### Файлы конфигурации -Только встроенные плагины имеют возможность настраивать исходные подсказки при создании нового проекта, а модули подсказок расположены [внутри пакета `@vue/cli`][prompt-modules]. +Теперь нужно предоставить файл конфигурации для UI: таким образом можно будет читать его содержимое и сохранять изменения обратно. Для этого указываем имя конфигурационного файла, его формат и указываем путь к нему: -Модуль подсказок должен экспортировать функцию, которая получает экземпляр [PromptModuleAPI][prompt-api]. Подсказки представлены с помощью [inquirer](https://github.com/SBoudrias/Inquirer.js) под капотом: +```js +api.describeConfig({ + // другие свойства конфигурации + files: { + myConfig: { + js: ['myConfig.js'] + } + } +}) +``` -``` js -module.exports = api => { - // объект возможности должен быть корректным объектом выбора inquirer - api.injectFeature({ - name: 'Какая-то суперская возможность', - value: 'my-feature' - }) +Можно указать больше одного файла. Например, если есть `myConfig.json`, можно определить его в свойстве `json: ['myConfig.json']`. Порядок здесь важен: первое имя файла в списке будет использоваться при создании файла конфигурации, если его не существует. - // injectPrompt ожидает корректный объект подсказки inquirer - api.injectPrompt({ - name: 'someFlag', - // убедитесь, что подсказка отображается только если выбрана ваша функция - when: answers => answers.features.include('my-feature'), - message: 'Вы хотите включить флаг foo?', - type: 'confirm' - }) +#### Отображение интерактивных подсказок конфигурации + +Отобразим поле ввода для отображения свойства с цветом на экране конфигурации. Для этого используем хук `onRead`, который вернёт список интерактивных подсказок для отображения: - // когда все подсказки завершены, внедряйте ваш плагин в настройки, - // которые будут передаваться генераторам - api.onPromptComplete((answers, options) => { - if (answers.features.includes('my-feature')) { - options.plugins['vue-cli-plugin-my-feature'] = { - someFlag: answers.someFlag +```js +api.describeConfig({ + // другие свойства конфигурации + onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Цвет сообщения с приветствием', + value: 'white' } - } + ] }) -} +}) ``` -#### Интерактивные подсказки для сторонних плагинов +Этот пример добавляет интерактивную подсказку в виде поля с указанным значением `white`. Вот как будет выглядеть экран конфигурации со всеми приведёнными выше настройками: -Плагины сторонних разработчиков обычно устанавливаются вручную после того, как проект уже создан, и пользователь будет инициализировать плагин вызовом команды `vue invoke`. Если плагин содержит `prompts.js` в своём корневом каталоге, он будет использован во время вызова. Файл должен экспортировать массив [вопросов](https://github.com/SBoudrias/Inquirer.js#question), которые будут обрабатываться Inquirer.js. Объект с ответами будет передаваться генератору плагина в качестве настроек. +![Начало конфигурации UI](/ui-config-start.png) -В качестве альтернативы, пользователь может пропустить подсказки и напрямую инициализировать плагин, передав параметры через командную строку, например: +Заменим теперь статическое значение `white` на свойство из конфигурационного файла. В хуке `onRead` объект `data` содержит JSON с результатом каждого файла конфигурации. В нашем случае содержание `myConfig.js` такое: -``` bash -vue invoke my-plugin --mode awesome +```js +// myConfig.js + +module.exports = { + color: 'black' +} ``` -## Распространение плагина +Поэтому объект `data` будет таким: -Чтобы CLI-плагин мог использоваться другими разработчиками, он должен быть опубликован на npm придерживаясь соглашения по именованию `vue-cli-plugin-`. Следуя соглашению по именованию позволит вашему плагину быть: +```js +{ + // Файл + myConfig: { + // Данные файла + color: 'black' + } +} +``` + +Легко увидеть, что необходимое нам свойство `data.myConfig.color`. Обновим хук `onRead`: -- Легко находимым с помощью `@vue/cli-service`; -- Легко находимым другими разработчиками через поиск; -- Устанавливаться через `vue add ` или `vue invoke `. +```js +// ui.js -## Примечание о разработке Core-плагинов +onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Цвет сообщения с приветствием', + value: data.myConfig && data.myConfig.color + } + ] +}), +``` -::: tip Примечание -Этот раздел применим только в случае, если вы работаете над встроенным плагином непосредственно внутри `vuejs/vue-cli` репозитория. +::: tip Совет +Обратите внимание, что `myConfig` может быть неопределён, если конфигурационного файла не существует на момент загрузки экрана конфигурации. ::: -Плагин с генератором, который внедряет дополнительные зависимости, отличные от пакетов в репозитории (например, `chai` внедряется `@vue/cli-plugin-unit-mocha/generator/index.js`) должен перечислять эти зависимости в собственном поле `devDependencies`. Это гарантирует: +Как можно увидеть на экране конфигурации значение `white` заменится на `black`. + +Также можно предоставить значение по умолчанию, если конфигурационный файл отсутствует: + +```js +// ui.js + +onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Цвет сообщения с приветствием', + value: data.myConfig && data.myConfig.color, + default: 'black', + } + ] +}), +``` + +#### Сохранение конфигурации после изменений + +Пока мы лишь считали содержимое `myConfig.js` и использовали его на экране конфигурации. Теперь попробуем сохранить все изменения в файл. Это можно сделать с помощью хука `onWrite`: + +```js +// ui.js + +api.describeConfig({ + /* ... */ + onWrite: ({ prompts, api }) => { + // ... + } +}) +``` + +Хук `onWrite` принимает множество [аргументов](ui-api.html#сохранение-изменений-конфигурации), но нам нужны только два из них: `prompts` и `api`. В первом текущие объекты интерактивных подсказок — получим id интерактивной подсказки и ответ для этого id. Для получения ответа воспользуемся методом `async getAnswer()` из `api`: + +```js +// ui.js + +async onWrite({ api, prompts }) { + const result = {} + for (const prompt of prompts) { + result[`${prompt.id}`] = await api.getAnswer(prompt.id) + } + api.setData('myConfig', result) +} +``` + +Теперь, если на экране конфигурации изменить значение цвета в поле ввода с `black` на `red` и нажать кнопку `Save the changes`, то содержимое файла `myConfig.js` также обновится: + +```js +// myConfig.js + +module.exports = { + color: 'red' +} +``` + +### Отображение интерактивных подсказок + +При желании также можно отображать [интерактивные подсказки](#интерактивные-подсказки) в Vue UI. При установке плагина через UI интерактивные подсказки будут отображаться на шаге вызова плагина. + +Объект подсказки можно расширять дополнительными свойствами. Они опциональны и используются только в UI: + +```js +// prompts.js + +module.exports = [ + { + // основные свойства интерактивных подсказок + name: `addExampleRoutes`, + type: 'confirm', + message: 'Добавить примеры маршрутов?', + default: false, + // свойства интерактивных подсказок для UI + group: 'Настоятельно рекомендуется', + description: 'Добавить примеры страниц, шаблонов и конфигурацию маршрутизатора', + link: 'https://github.com/ktsn/vue-cli-plugin-auto-routing/#vue-cli-plugin-auto-routing' + } +] +``` + +В результате при вызове плагина появится такой экран: + +![Интерактивные подсказки в UI](/ui-prompts.png) + +### Логотип + +Можно поместить файл `logo.png` в корне каталога, который будет публиковаться в npm. Тогда его можно будет увидеть в нескольких местах: + - При поиске плагина для установки + - В списке установленных плагинов + - В списке конфигураций (по умолчанию) + - В списке дополненных задач (по умолчанию) -1. что пакет всегда существует в корневом `node_modules` репозитория, поэтому нам не нужно их переустанавливать при каждом тестировании. +![Плагины](/plugins.png) -2. что `yarn.lock` остаётся постоянным, поэтому CI сможет лучше применять его кэширование. +Логотип должен быть квадратным изображением без прозрачности (в идеале 84x84). + +## Публикация плагина в npm + +Для публикации плагина необходимо быть зарегистрированным на [npmjs.com](https://www.npmjs.com) и глобально установить `npm`. Если публикуете ваш первый npm-модуль, то сначала запустите команду: + +```bash +npm login +``` + +Введите имя пользователя и пароль. Это позволит сохранить учётные данные, чтобы не приходилось вводить их при каждой публикации. + +:::tip Совет +Перед публикацией плагина убедитесь, что выбрали правильное имя для него! Соглашение по именованию `vue-cli-plugin-`. Дополнительную информации см. в разделе [Именование и обнаруживаемость в поиске](#именование-и-обнаруживаемость-в-поиске). +::: + +Для публикации плагина перейдите в корневой каталог и выполните команду в терминале: + +```bash +npm publish +``` -[creator-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/Creator.js -[service-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/Service.js -[generator-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/GeneratorAPI.js -[commands]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/commands -[config]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/config -[plugin-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/PluginAPI.js -[prompt-modules]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/promptModules -[prompt-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/PromptModuleAPI.js +После успешной публикации можно будет добавить ваш плагин в проект, созданный с помощью Vue CLI командой `vue add `. diff --git a/docs/ru/dev-guide/ui-api.md b/docs/ru/dev-guide/ui-api.md index 91d657e756..bb6f6d289d 100644 --- a/docs/ru/dev-guide/ui-api.md +++ b/docs/ru/dev-guide/ui-api.md @@ -526,7 +526,7 @@ api.addTask({ } ``` -Поддерживаемые inquirer типы: `checkbox`, `confirm`, `input`, `password`, `list`, `rawlist`. +Поддерживаемые inquirer-типы: `checkbox`, `confirm`, `input`, `password`, `list`, `rawlist`, `editor`. В дополнение к ним пользовательский интерфейс поддерживает специальные типы, которые работают только с ним: @@ -628,7 +628,7 @@ api.addTask({ ### Создание клиентского дополнения -Рекомендуемый способ создания клиентского дополнения — создать новый проект с помощью vue cli. Вы можете либо сделать это в подкаталоге вашего плагина, либо в другом npm пакете. +Рекомендуемый способ создания клиентского дополнения — создать новый проект с помощью Vue CLI. Вы можете либо сделать это в подкаталоге вашего плагина, либо в другом npm пакете. Установите `@vue/cli-ui` в качестве зависимости для разработки (dev dependency). @@ -790,7 +790,7 @@ api.addView({ id: 'org.vue.webpack.views.test', // Имя маршрута (из vue-router) - // Использует то же имя, как и в методе 'ClientAddonApi.addRoutes' (см. выше в разлеле клиентское дополнение) + // Использует то же имя, как и в методе 'ClientAddonApi.addRoutes' (см. выше в разделе клиентское дополнение) name: 'org.vue.webpack.routes.test', // Иконка кнопки (material-icons) @@ -1027,7 +1027,7 @@ ipc.send({ ... }) ipc.connect() ``` -Автоотключение при простое (спустя некоторое время без отправляемых сообщений): +Авто-отключение при простое (спустя некоторое время без отправляемых сообщений): ```js const ipc = new IpcMessenger({ diff --git a/docs/ru/dev-guide/ui-localization.md b/docs/ru/dev-guide/ui-localization.md index bb5c9c1db8..aa56a65992 100644 --- a/docs/ru/dev-guide/ui-localization.md +++ b/docs/ru/dev-guide/ui-localization.md @@ -7,7 +7,7 @@ Для существующих переводов, вы можете [зарегистрироваться в качестве переводчика](https://www.transifex.com/vuejs/vue-cli/dashboard/). Для новых переводов, вы можете [запросить добавление нового языка](https://www.transifex.com/vuejs/vue-cli/dashboard/) после регистрации. -В любом случае вы можете переводить ключи по мере их добавления или изменения в исходной локализции. +В любом случае вы можете переводить ключи по мере их добавления или изменения в исходной локализации. ## Локализация вашего плагина diff --git a/docs/ru/guide/browser-compatibility.md b/docs/ru/guide/browser-compatibility.md index 4427893b14..816ea924a6 100644 --- a/docs/ru/guide/browser-compatibility.md +++ b/docs/ru/guide/browser-compatibility.md @@ -18,16 +18,16 @@ 1. **Если зависимость написана в версии ES, которую не поддерживают целевые окружения:** Добавьте эту зависимость в опцию [`transpileDependencies`](../config/#transpiledependencies) в файле `vue.config.js`. Это позволит использовать как синтаксические преобразования, так и определение полифилов для используемых возможностей для этой зависимости. -2. **Если зависимость предоставляет ES5 код и явно перечисляет необходимые полифилы:** вы можете предварительно включить необходимые полифилы с помощью опции [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) для `@vue/babel-preset-app`. **Обратите внимание, что `es6.promise` добавлен по умолчанию, так как он часто необходим для библиотек, основанных на Promise.** +2. **Если зависимость предоставляет ES5 код и явно перечисляет необходимые полифилы:** вы можете предварительно включить необходимые полифилы с помощью опции [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) для `@vue/babel-preset-app`. **Обратите внимание, что `es.promise` добавлен по умолчанию, так как он часто необходим для библиотек, основанных на Promise.** - ``` js + ```js // babel.config.js module.exports = { presets: [ ['@vue/app', { polyfills: [ - 'es6.promise', - 'es6.symbol' + 'es.promise', + 'es.symbol' ] }] ] @@ -38,9 +38,9 @@ Рекомендуется добавлять полифилы таким образом, а не напрямую импортировать их в коде, потому что полифилы перечисленные здесь, могут быть автоматически исключены, если целевым браузерам, указанным в `browserslist`, они не нужны. ::: -3. **Если зависимость предоставляет ES5 код, но использует возможности ES6+ без явного перечисления необходимых полифилов (например, Vuetify):** Используйте `useBuiltIns: 'entry'` и затем добавьте `import '@babel/polyfill'` в файл точки входа. Это будет импортировать **ВСЕ** полифилы на основе целей, перечисленных в `browserslist`, так что вам больше не нужно будет беспокоиться о полифилах для зависимостей, но это скорее всего увеличит размер финальной сборки некоторыми неиспользуемыми полифилами. +3. **Если зависимость предоставляет ES5 код, но использует возможности ES6+ без явного перечисления необходимых полифилов (например, Vuetify):** Используйте `useBuiltIns: 'entry'` и затем добавьте `import 'core-js/stable'; import 'regenerator-runtime/runtime';` в файл точки входа. Это будет импортировать **ВСЕ** полифилы на основе целей, перечисленных в `browserslist`, так что вам больше не нужно будет беспокоиться о полифилах для зависимостей, но это скорее всего увеличит размер финальной сборки некоторыми неиспользуемыми полифилами. -Подробнее можно изучить в [документации @babel-preset/env](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage). +Подробнее можно изучить в [документации @babel/preset-env](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage). ### Полифилы при сборке библиотеки или веб-компонентов @@ -52,7 +52,7 @@ Vue CLI предоставляет «Современный режим», чтобы помочь в решении этой проблемы. При сборке для production с помощью следующей команды: -``` bash +```bash vue-cli-service build --modern ``` @@ -89,6 +89,12 @@ Vue CLI использует две переменных окружения дл **Важно:** Переменные доступны только при вызове/после вызова функций `chainWebpack()` и `configureWebpack()`, (т.е. не напрямую в области видимости модуля `vue.config.js`). Это также означает, что они доступны в файле конфигурации postcss. ::: +::: warning Предостережение: Настройка плагинов webpack +Некоторые плагины, такие как `html-webpack-plugin`, `preload-plugin` и т.п., добавляются только в конфигурации для современного режима. Попытка использовать их опции в конфигурации для старых браузеров может привести к ошибке, так как этих плагинов не будет существовать. + +Используйте совет выше об *Определении текущего режима в конфигурации* для управления плагинами только в соответствующем режиме, и/или проверяйте наличие плагина в конфигурации текущего режима, прежде чем использовать их опции. +::: + [autoprefixer]: https://github.com/postcss/autoprefixer [babel-preset-env]: https://new.babeljs.io/docs/en/next/babel-preset-env.html [babel-preset-app]: https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app diff --git a/docs/ru/guide/build-targets.md b/docs/ru/guide/build-targets.md index f1918710f3..c1111262d2 100644 --- a/docs/ru/guide/build-targets.md +++ b/docs/ru/guide/build-targets.md @@ -8,15 +8,11 @@ - `index.html` с внедрением ресурсов и подсказок для пред-загрузки - сторонние библиотеки разделяются на отдельные фрагменты для лучшего кэширования -- статические ресурсы менее 4 КБайт будут вставлены инлайн в JavaScript +- статические ресурсы менее 8 КБайт будут вставлены инлайн в JavaScript - статические ресурсы в `public` будут скопированы в каталог сборки ## Библиотека (Library) -::: tip Примечание о совместимости с IE -В режиме библиотеки публичный путь [определяется динамически](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/commands/build/setPublicPath.js) по URL-адресу, по которому загружается основной js-файл (для включения динамической загрузки ресурсов). Но эта функциональность использует `document.currentScript`, который отсутствует в IE. Поэтому рекомендуем добавлять [current-script-polyfill](https://www.npmjs.com/package/current-script-polyfill) в финальную веб-страницу перед импортом библиотеки, если требуется поддержка IE. -::: - ::: tip Примечание о зависимости Vue В режиме библиотеки Vue *экстернализируется*. Это означает, что сборка не будет содержать Vue, даже если ваш код его импортирует. Если библиотека используется через сборщик, он должен попытаться загрузить Vue в качестве зависимости через сборщик; в противном случае, он должен вернуться к глобальной переменной `Vue`. @@ -54,13 +50,17 @@ dist/myLib.css 0.33 kb 0.23 kb - `dist/myLib.css`: извлечённый CSS-файл (можно принудительно вставлять стили инлайн, установив `css: { extract: false }` в `vue.config.js`) +::: warning ВНИМАНИЕ +При разработке библиотеки или использования монорепозитория, имейте ввиду, что CSS-импорты **являются побочными эффектами (side effects)**. Убедитесь, что **удалили** опцию `"sideEffects": false` из файла `package.json`, в противном случае webpack будет удалять CSS-фрагменты при сборке для production. +::: + ### Vue vs. JS / TS файлы точек входа При использовании `.vue` файла в качестве точки входа, библиотека будет экспортировать сам компонент Vue, потому что компонент всегда имеет экспорт по умолчанию (export default). Однако, когда используется `.js` или `.ts` файл в качестве точки входа, он может содержать именованные экспорты, поэтому библиотека будет использоваться как модуль. Это означает, что экспорт библиотеки по умолчанию должен быть доступен как `window.yourLib.default` в UMD сборках, или как `const myLib = require('mylib').default` в CommonJS сборках. Если у вас нет именованных экспортов и вы хотите использовать экспорт по умолчанию (default export), вы можете использовать следующую конфигурацию webpack в `vue.config.js`: -``` js +```js module.exports = { configureWebpack: { output: { @@ -100,7 +100,7 @@ vue-cli-service build --target wc --name my-element [entry] Этот режим позволяет использовать компонент Vue как обычный элемент DOM: -``` html +```html @@ -139,10 +139,28 @@ dist/foo.1.js 5.24 kb 1.64 kb Теперь на странице пользователю необходимо только подключить Vue и файл точки входа: -``` html +```html ``` + +## Использование vuex в сборках + +При создании [Веб-компонента](#веб-компонент-web-component) или [Библиотеки](#бибnиотека-library), точкой входа будет не `main.js`, а файл `entry-wc.js`, генерируемый здесь: [https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/commands/build/resolveWcEntry.js](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/commands/build/resolveWcEntry.js) + +Поэтому для использования vuex при сборке веб-компонента необходимо инициализировать хранилище в `App.vue`: + +```js +import store from './store' + +// ... + +export default { + store, + name: 'App', + // ... +} +``` diff --git a/docs/ru/guide/cli-service.md b/docs/ru/guide/cli-service.md index c054b09e8f..e0a66d21c5 100644 --- a/docs/ru/guide/cli-service.md +++ b/docs/ru/guide/cli-service.md @@ -6,7 +6,7 @@ Это то, что вы увидите в `package.json` проекта с пресетом настроек по умолчанию: -``` json +```json { "scripts": { "serve": "vue-cli-service serve", @@ -17,7 +17,7 @@ Вы можете вызвать эти команды с помощью npm или Yarn: -``` bash +```bash npm run serve # ИЛИ yarn serve @@ -25,8 +25,8 @@ yarn serve Если у вас установлен [npx](https://github.com/npm/npx) (должен поставляться в комплекте с последней версией npm), то вы также можете запустить бинарник напрямую: -``` bash -npx vue-cli-service serve +```bash +npx --no vue-cli-service serve ``` ::: tip Совет @@ -44,18 +44,26 @@ npx vue-cli-service serve Опции: - --open открыть браузер при запуске сервера - --copy скопировать url в буфер обмена при запуске сервера - --mode определить режим сборки (по умолчанию: development) - --host определить хост (по умолчанию: 0.0.0.0) - --port определить порт (по умолчанию: 8080) - --https использовать https (по умолчанию: false) + --open открыть браузер при запуске сервера + --copy скопировать url в буфер обмена при запуске сервера + --mode определить режим сборки (по умолчанию: development) + --host определить хост (по умолчанию: 0.0.0.0) + --port определить порт (по умолчанию: 8080) + --https использовать https (по умолчанию: false) + --public указать URL-адрес публичной сети для клиента HMR + --skip-plugins имена плагинов через запятую, которые следует пропустить при запуске ``` +::: tip --copy +Копирование в буфер обмена может не работать на некоторых платформах. Если копирование выполнилось успешно, то рядом с URL-адресом локального сервера разработки будет отображено `(copied to clipboard)`. +::: + Команда `vue-cli-service serve` запускает сервер для разработки (основанный на [webpack-dev-server](https://github.com/webpack/webpack-dev-server)), предоставляющий из коробки функцию горячей замены модулей. Кроме флагов командной строки, также можно настраивать сервер для разработки с помощью поля [devServer](../config/#devserver) в файле `vue.config.js`. +В команде CLI `[entry]` означает *входной файл* (по умолчанию: `src/main.js` или `src/main.ts` в проектах с TypeScript), а не *дополнительный входной файл*. Если вы перезапишете запись в CLI, тогда записи из `config.pages` больше не будут учитываться, что может привести к ошибке. + ## vue-cli-service build ``` @@ -63,19 +71,23 @@ npx vue-cli-service serve Опции: - --mode определить режим сборки (по умолчанию: production) - --dest определить каталог сборки (по умолчанию: dist) - --modern собирать приложение для современных браузеров с авто-фоллбэком для старых - --target app | lib | wc | wc-async (по умолчанию: app) - --inline-vue включить Vue в содержимое сборки библиотеки или веб-компонента - --name имя библиотеки или режим веб-компонента (по умолчанию: "name" в package.json или имя файла точки входа) - --no-clean не удалять каталог dist перед сборкой проекта - --report сгенерировать report.html для анализа содержимого сборки - --report-json сгенерировать report.json для анализа содержимого сборки - --watch отслеживать изменения + --mode определить режим сборки (по умолчанию: production) + --dest определить каталог сборки (по умолчанию: dist) + --modern собирать приложение для современных браузеров с авто-фоллбэком для старых + --no-unsafe-inline собирать приложение без внедрения инлайн-скриптов + --target app | lib | wc | wc-async (по умолчанию: app) + --formats список выходных форматов для сборок библиотек (по умолчанию: commonjs,umd,umd-min) + --inline-vue включить Vue в содержимое сборки библиотеки или веб-компонента + --name имя библиотеки или режим веб-компонента (по умолчанию: "name" в package.json или имя файла точки входа) + --filename имя выходного файла, только для 'lib' (по умолчанию: значение --name) + --no-clean не удалять каталог dist перед сборкой проекта + --report сгенерировать report.html для анализа содержимого сборки + --report-json сгенерировать report.json для анализа содержимого сборки + --skip-plugins имена плагинов через запятую, которые следует пропустить при запуске + --watch отслеживать изменения ``` -Команда `vue-cli-service build` создаёт готовую для production-сборку в каталоге `dist/` с минификацией для JS / CSS / HTML и автоматическим разделением вендорного кода в отдельный фрагмент для лучшего кэширования. Манифест фрагмента вставляется инлайн в HTML. +Команда `vue-cli-service build` создаёт готовую для production сборку в каталоге `dist/` с минификацией для JS / CSS / HTML и автоматическим разделением вендорного кода в отдельный фрагмент для лучшего кэширования. Манифест фрагмента вставляется инлайн в HTML. Есть несколько полезных флагов: @@ -101,14 +113,41 @@ npx vue-cli-service serve Некоторые плагины CLI добавляют собственные команды в `vue-cli-service`. Например, `@vue/cli-plugin-eslint` внедряет команду `vue-cli-service lint`. Вы можете посмотреть весь список команд запустив: -``` bash -npx vue-cli-service help +```bash +npx --no vue-cli-service help ``` Вы также можете узнать о доступных параметрах каждой команды с помощью: -``` bash -npx vue-cli-service help [command] +```bash +npx --no vue-cli-service help [command] +``` + +## Исключение плагинов при запуске + +Можно исключить определённые плагины при запуске команды, передав имя плагина опцией `--skip-plugins`. + +```bash +npx --no vue-cli-service build --skip-plugins pwa +``` + +::: tip СОВЕТ +Опция доступна для _любых_ команд `vue-cli-service`, в том числе и для пользовательских команд, добавленных другими плагинами. +::: + +Можно пропустить несколько подключаемых плагинов, передав их имена через запятую: + +```bash +npx --no vue-cli-service build --skip-plugins pwa,apollo +``` + +Имена плагинов разрешаются также, как и при установке, что подробнее описано [здесь](./plugins-and-presets.md#установка-пnагинов-в-существующий-проект) + +```bash +# все вызовы равнозначны +npx --no vue-cli-service build --skip-plugins pwa +npx --no vue-cli-service build --skip-plugins @vue/pwa +npx --no vue-cli-service build --skip-plugins @vue/cli-plugin-pwa ``` ## Кэширование и параллелизация @@ -121,10 +160,13 @@ npx vue-cli-service help [command] После установки `@vue/cli-service` также добавляется [yorkie](https://github.com/yyx990803/yorkie), который позволяет легко указывать Git хуки, используя поле `gitHooks` в файле `package.json`: -``` json +```json { "gitHooks": { "pre-commit": "lint-staged" + }, + "lint-staged": { + "*.{js,vue}": "vue-cli-service lint" } } ``` diff --git a/docs/ru/guide/creating-a-project.md b/docs/ru/guide/creating-a-project.md index fbfebcfef6..bfe29626aa 100644 --- a/docs/ru/guide/creating-a-project.md +++ b/docs/ru/guide/creating-a-project.md @@ -4,7 +4,7 @@ Для создания нового проекта запустите команду: -``` bash +```bash vue create hello-world ``` @@ -28,12 +28,12 @@ vue create hello-world ::: tip ~/.vuerc Создаваемые пресеты сохраняются в JSON-файле `.vuerc` в домашнем каталоге вашего пользователя. Если вы захотите изменить сохранённые пресеты / настройки, можете это сделать отредактировав этот файл. -В процессе создания проекта, также может быть предложено выбрать предпочитаемый менеджер пакетов или использовать [Taobao зеркало для npm регистра](https://npm.taobao.org/), чтобы ускорить установку зависимостей. Этот выбор также будет сохранён в `~/.vuerc`. +В процессе создания проекта, также может быть предложено выбрать предпочитаемый менеджер пакетов или использовать [Taobao зеркало для npm регистра](https://npmmirror.com/), чтобы ускорить установку зависимостей. Этот выбор также будет сохранён в `~/.vuerc`. ::: Команда `vue create` предоставляет множество опций — вы можете изучить их все выполнив: -``` bash +```bash vue create --help ``` @@ -63,7 +63,7 @@ vue create --help Вы можете создавать и управлять проектами через графический интерфейс командой `vue ui`: -``` bash +```bash vue ui ``` @@ -75,7 +75,7 @@ vue ui Vue CLI >= 3 использует команду `vue`, поэтому он перезаписывает Vue CLI 2 (`vue-cli`). Если вам по-прежнему необходимо старое поведение и функциональность команды `vue init`, нужно лишь установить глобально дополнительный плагин `@vue/cli-init`: -``` bash +```bash npm install -g @vue/cli-init # vue init теперь работает точно также, как и в vue-cli@2.x vue init webpack my-project diff --git a/docs/ru/guide/css.md b/docs/ru/guide/css.md index 92d3a06a14..f4032ada72 100644 --- a/docs/ru/guide/css.md +++ b/docs/ru/guide/css.md @@ -10,9 +10,9 @@ Вы можете выбрать пре-процессоры (Sass/Less/Stylus) при создании проекта. Если вы этого не сделали, то внутренняя конфигурация webpack всё равно настроена для их использования. Вам лишь требуется вручную доустановить соответствующие загрузчики для webpack: -``` bash +```bash # Sass -npm install -D sass-loader node-sass +npm install -D sass-loader sass # Less npm install -D less-loader less @@ -21,14 +21,33 @@ npm install -D less-loader less npm install -D stylus-loader stylus ``` +:::tip Примечание при использовании webpack 4 +При использовании `webpack` версии 4, по умолчанию во Vue CLI 4, следует убедиться в совместимости используемых загрузчиков. В противном случае будут появляться ошибки о конфликтующих зависимостях. В таких случаях можно использовать более старую версию загрузчика, которая всё ещё совместима с `webpack` 4. + +```bash +# Sass +npm install -D sass-loader@^10 sass +``` +::: + Теперь вы можете импортировать соответствующие типы файлов, или использовать их синтаксис внутри файлов `*.vue` с помощью: -``` vue +```vue ``` +::: tip Совет по производительности Sass +Обратите внимание, при использовании Dart Sass **синхронная компиляция вдвое быстрее асинхронной** по умолчанию, из-за накладных расходов на асинхронные коллбэки. Чтобы избежать их можно воспользоваться пакетом [fibers](https://www.npmjs.com/package/fibers) для вызова асинхронных импортёров по пути синхронного кода. Для этого просто установите `fibers` в качестве зависимости проекта: + +``` +npm install -D fibers +``` + +Также имейте в виду, поскольку это нативный модуль, то могут возникнуть различные проблемы совместимости, в зависимости от ОС и окружения сборки. В таких случаях выполните `npm uninstall -D fibers` для устранения проблемы. +::: + ### Автоматические импорты Если вы хотите автоматически импортировать файлы (для цветов, переменных, примесей...), можно использовать [style-resources-loader](https://github.com/yenshih/style-resources-loader). Вот пример для stylus, который импортирует `./src/styles/imports.styl` в каждый однофайловый компонент и в каждый файл stylus: @@ -75,33 +94,38 @@ Vue CLI использует PostCSS внутри себя. Для импорта CSS или других файлов пре-процессоров в качестве CSS-модулей в JavaScript, необходимо чтобы имя файла заканчивалось на `.module.(css|less|sass|scss|styl)`: -``` js +```js import styles from './foo.module.css' // работает для всех поддерживаемых пре-процессоров import sassStyles from './foo.module.scss' ``` -Если вы не хотите указывать `.module` в именах файлов, установите `css.modules` в `true` внутри файла `vue.config.js`: +Если вы не хотите указывать `.module` в именах файлов, установите `css.requireModuleExtension` в `false` внутри файла `vue.config.js`: -``` js +```js // vue.config.js module.exports = { css: { - modules: true + requireModuleExtension: false } } ``` Если вы хотите настроить генерируемые имена классов для CSS-модулей, вы можете сделать это с помощью опции `css.loaderOptions.css` в `vue.config.js`. Все настройки `css-loader` поддерживаются, например `localIdentName` и `camelCase`: -``` js +```js // vue.config.js module.exports = { css: { loaderOptions: { css: { - localIdentName: '[name]-[hash]', - camelCase: 'only' + // Примечание: формат конфигурации отличается между Vue CLI v4 и v3 + // Для пользователей Vue CLI v3, обратитесь к документации css-loader v1 + // https://github.com/webpack-contrib/css-loader/tree/v1.0.1 + modules: { + localIdentName: '[name]-[hash]' + }, + localsConvention: 'camelCaseOnly' } } } @@ -112,16 +136,25 @@ module.exports = { Иногда может возникнуть необходимость передать настройки в загрузчик пре-процессора для webpack. Вы можете сделать это с помощью опции `css.loaderOptions` в `vue.config.js`. Например, для передачи глобальных переменных во все стили Sass/Less: -``` js +```js // vue.config.js module.exports = { css: { loaderOptions: { // передача настроек в sass-loader + // @/ это псевдоним к каталогу src/ поэтому предполагается, + // что у вас в проекте есть файл `src/variables.scss` + // Примечание: эта опция называется "prependData" в sass-loader v8 sass: { - // @/ это псевдоним к каталогу src/ поэтому предполагается, - // что у вас в проекте есть файл `src/variables.scss` - data: `@import "~@/variables.scss";` + additionalData: `@import "~@/variables.sass"` + }, + // по умолчанию опция `sass` будет применяться к обоим синтаксисам + // потому что синтаксис `scss` по сути также обрабатывается sass-loader + // но при настройке опции `prependData` синтаксис `scss` требует точку с запятой + // в конце оператора, в то время как для `sass` точки с запятой не требуется + // в этом случае синтаксис `scss` можно настроить отдельно с помощью опции `scss` + scss: { + additionalData: `@import "~@/variables.scss";` }, // передача настроек Less.js в less-loader less:{ diff --git a/docs/ru/guide/deployment.md b/docs/ru/guide/deployment.md index 8d9bb722f2..632fb9e005 100644 --- a/docs/ru/guide/deployment.md +++ b/docs/ru/guide/deployment.md @@ -10,7 +10,7 @@ Каталог `dist` предназначен для обслуживания HTTP-сервером (если не задали `publicPath` относительным значением), поэтому не сработает если напрямую открыть `dist/index.html` через `file://` протокол. Самый простой способ предпросмотра вашей сборки для production локально — использовать статический файловый сервер Node.js, например [serve](https://github.com/zeit/serve): -``` bash +```bash npm install -g serve # флаг -s означает запуск serve в режиме одностраничного приложения (SPA) # который решает проблему маршрутизации, описанную ниже @@ -35,13 +35,17 @@ serve -s dist ### GitHub Pages +#### Публикация обновлений вручную + 1. Установите корректное значение `publicPath` в `vue.config.js`. - Если вы публикуете по адресу `https://.github.io/`, вы можете опустить `publicPath`, так как оно по умолчанию `"/"`. + Если публикуете по адресу `https://.github.io/` или на пользовательский домен, то можно опустить `publicPath`, так как оно по умолчанию `"/"`. Если вы публикуете по адресу `https://.github.io//`, (т.е. ваш репозиторий находится по адресу `https://github.com//`), установите `publicPath` в значение `"//"`. Например, если ваш репозиторий называется "my-project", то ваш `vue.config.js` будет выглядеть примерно так: - ``` js + ```js + // файл vue.config.js должен быть расположен в корневом каталоге проекта + module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/my-project/' @@ -51,7 +55,7 @@ serve -s dist 2. Внутри вашего проекта создайте `deploy.sh` со следующим содержимым (при необходимости раскомментировав подсвеченные строки) и запустите его для публикации: - ``` bash{13,20,23} + ```bash{13,20,23} #!/usr/bin/env sh # остановить публикацию при ошибках @@ -79,9 +83,34 @@ serve -s dist cd - ``` - ::: tip Совет - Вы также можете запустить скрипт выше в вашей конфигурации CI чтобы включить автоматическую публикацию на каждый push в репозиторий. - ::: +#### Использование Travis CI для автоматических обновлений + +1. Установите правильный `publicPath` в `vue.config.js`, как описано выше. + +2. Установите клиент Travis CLI: `gem install travis && travis --login` + +3. Сгенерируйте [токен доступа](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) на GitHub с правами доступа к репозиторию. + +4. Разрешите доступ Travis к репозиторию: `travis env set GITHUB_TOKEN xxx` (`xxx` — это персональный токен доступа из шага 3.) + +5. Создайте файл `.travis.yml` в корневом каталоге проекта. + + ```yaml + language: node_js + node_js: + - "node" + cache: npm + script: npm run build + deploy: + provider: pages + skip_cleanup: true + github_token: $GITHUB_TOKEN + local_dir: dist + on: + branch: master + ``` + +6. Добавьте файл `.travis.yml` в репозиторий, чтобы запустить первую сборку. ### GitLab Pages @@ -98,6 +127,8 @@ pages: # задание должно быть именованными стра - npm run build - mv public public-vue # GitLab Pages хук для каталога public - mv dist public # переименование каталога dist (результат команды npm run build) + # опционально, можно активировать поддержку gzip с помощью следующей строки: + - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \; artifacts: paths: - public # путь к артефакту должен быть /public для GitLab Pages @@ -105,15 +136,14 @@ pages: # задание должно быть именованными стра - master ``` -Как правило, по адресу `https://yourUserName.gitlab.io/yourProjectName` будет располагаться статический веб-сайт, поэтому вы также захотите создать файл `vue.config.js` для указания [значения `BASE_URL`](../config/#publicpath), соответствующего ему: +Как правило, по адресу `https://yourUserName.gitlab.io/yourProjectName` будет располагаться статический веб-сайт, поэтому потребуется создать файл `vue.config.js` для указания [значения `BASE_URL`](../config/#publicpath), соответствующего имени проекта ([переменная окружения `CI_PROJECT_NAME`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) содержит его): ```javascript // файл vue.config.js расположен в корне вашего репозитория -// убедитесь, что обновили `yourProjectName` на имя вашего проекта GitLab module.exports = { publicPath: process.env.NODE_ENV === 'production' - ? '/yourProjectName/' + ? '/' + process.env.CI_PROJECT_NAME + '/' : '/' } ``` @@ -126,21 +156,77 @@ module.exports = { 1. На сайте Netlify добавьте новый проект из GitHub со следующими настройками: - - **Build Command:** `npm run build` или `yarn build` - - **Publish directory:** `dist` + - **Build Command:** `npm run build` или `yarn build` + - **Publish directory:** `dist` 2. Нажмите кнопку публикации! Также посмотрите [vue-cli-plugin-netlify-lambda](https://github.com/netlify/vue-cli-plugin-netlify-lambda). -Для получения прямых хитов при использовании `режима history` во Vue Router, необходимо создавть файл `_redirects` в каталоге `/public` со следующим содержимым: +#### Использование режима history во Vue Router + +Для получения прямых хитов при использовании `режима history` во Vue Router, необходимо перенаправлять весь трафик в файл `/index.html`. + +> Подробнее можно изучить в [документации Netlify по перенаправлениям](https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps). + +##### Рекомендуемый метод + +Создать файл `netlify.toml` в корневом каталоге репозитория со следующим содержимым: + +```toml +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 +``` + +##### Альтернативный метод + +Создать файл `_redirects` в каталоге `/public` со следующим содержимым: ``` # Настройки Netlify для одностраничных приложений (SPA) /* /index.html 200 ``` -Подробнее можно изучить в [документации Netlify по перенаправлениям](https://www.netlify.com/docs/redirects/#history-pushstate-and-single-page-apps). +При использовании [@vue/cli-plugin-pwa](../core-plugins/pwa.md#vue-cli-plugin-pwa) убедитесь, что файл `_redirects` не кэшируется service worker. + +Для этого добавьте в `vue.config.js` следующее: + +```js +// файл vue.config.js должен быть расположен в корневом каталоге проекта + +module.exports = { + pwa: { + workboxOptions: { + exclude: [/_redirects/] + } + } +} +``` + +Подробнее об опциях [workboxOptions](../core-plugins/pwa.md#configuration) и [exclude](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.InjectManifest#InjectManifest). + +### Render + +[Render](https://render.com) предлагает [бесплатный хостинг статических сайтов](https://render.com/docs/static-sites) с полностью управляемым SSL, глобальным CDN и непрерывным автоматическим развёртыванием из GitHub. + +1. Создайте новый Static Site в Render, и предоставьте доступ для GitHub-приложения Render в репозиторий. + +2. При создании используйте следующие значения: + + - **Команда сборки:** `npm run build` или `yarn build` + - **Каталог публикации:** `dist` + +Всё! Приложение будет доступно по URL-адресу Render сразу, как только завершится сборка. + +Для того, чтобы получать прямые хиты при использовании режима history во Vue Router, необходимо добавить следующее правило на вкладке `Redirects/Rewrites` вашего сайта. + + - **Источник:** `/*` + - **Назначение:** `/index.html` + - **Статус** `Rewrite` + +Узнайте больше о настройке [перенаправлений, перезаписей](https://render.com/docs/redirects-rewrites) и [пользовательских доменах](https://render.com/docs/custom-domains) на Render. ### Amazon S3 @@ -205,55 +291,44 @@ firebase deploy --only hosting Если вы хотите использовать другие возможности Firebase CLI, которые вы используете в своём проекте для публикации, запустите `firebase deploy` без опции `--only`. -Теперь можно открыть проект по адресу `https://.firebaseapp.com`. +Теперь можно открыть проект по адресу `https://.firebaseapp.com` или `https://.web.app`. Обратитесь к [документации Firebase](https://firebase.google.com/docs/hosting/deploying) для получения более подробной информации. -### Now +### Vercel -1. Установите глобально Now CLI: +[Vercel](https://vercel.com/home) — облачная платформа, позволяющая разработчикам хостить Jamstack веб-сайты и веб-сервисы, которые публикуются мгновенно, автоматически масштабируются и не требуют никакого контроля, всё это с zero-конфигурацией. Они обеспечивают глобальный доступ, SSL-шифрование, сжатие ресурсов, инвалидацию кэша и многое другое. -```bash -npm install -g now -``` +#### Шаг 1: Публикация проекта Vue на Vercel -2. Добавьте файл `now.json` в корневой каталог проекта: +Для публикации проекта Vue с помощью [Vercel для интеграции с Git](https://vercel.com/docs/git-integrations), убедитесь, что он был выложен в Git-репозиторий. - ```json - { - "name": "my-example-app", - "type": "static", - "static": { - "public": "dist", - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - "alias": "vue-example", - "files": [ - "dist" - ] - } - ``` +Импортируйте проект в Vercel с помощью [Import Flow](https://vercel.com/import/git). Во время импорта будут запрошены все соответствующие [опции](https://vercel.com/docs/build-step#build-&-development-settings), предварительно сконфигурированные, но с возможностью изменения при необходимости. - Можно детальнее настроить статическую публикацию, обратившись к [документации Now](https://zeit.co/docs/deployment-types/static). +После импорта проекта, все последующие push в ветку будут генерировать [публикации для предпросмотра](https://vercel.com/docs/platform/deployments#preview), а все изменения внесённые в [ветку Production](https://vercel.com/docs/git-integrations#production-branch) (обычно "master" или "main") будут приводить к [публикации Production](https://vercel.com/docs/platform/deployments#production). -3. Добавьте скрипт для публикации в `package.json`: +После публикации вы получите URL-адрес для просмотра приложения вживую, например: https://vue-example-tawny.vercel.app/. - ```json - "deploy": "npm run build && now && now alias" - ``` +#### Шаг 2 (опционально): Использование пользовательского домена - Если вы хотите по умолчанию публиковать публично, то измените команду на следующую: +При необходимости использовать пользовательский домен при публикации Vercel, можно **Добавить** или **Перенаправить** домен через [настройки домена аккаунта](https://vercel.com/dashboard/domains) Vercel. - ```json - "deploy": "npm run build && now --public && now alias" - ``` +Для добавления домена в проект, перейдите в раздел [Проект](https://vercel.com/docs/platform/projects) на панели Vercel. После выбора проекта перейдите на вкладку "Настройки", затем выберите пункт меню **Домены**. На странице **Домен** вашего проекта, укажите домен которые хотите использовать в проекте. - Это автоматически установит псевдоним сайта на последнюю публикацию. Теперь просто запустите команду `npm run deploy` для публикации приложения. +После добавления домена, будут предоставлены различные методы его настройки. + +#### Публикация свежего проекта на Vue + +Для публикации свежего проекта на Vue с настроенным Git-репозиторием, можно с помощью кнопки Deploy ниже: + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmaster%2Fexamples%2Fvue) + +## Ресурсы: + +- [Пример исходного кода](https://github.com/vercel/vercel/tree/master/examples/vue) +- [Официальное руководство Vercel](https://vercel.com/guides/deploying-vuejs-to-vercel) +- [Руководство по публикации Vercel](https://vercel.com/docs) +- [Документация по пользовательским доменам Vercel](https://vercel.com/docs/custom-domains) ### Stdlib @@ -261,7 +336,38 @@ npm install -g now ### Heroku -> TODO | Присылайте пулл-реквесты. +1. [Установите Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) + +2. Создайте файл `static.json`: + + ```json + { + "root": "dist", + "clean_urls": true, + "routes": { + "/**": "index.html" + } + } + ``` + +3. Добавьте файл `static.json` в git + + ```bash + git add static.json + git commit -m "add static configuration" + ``` + +4. Запустите публикацию на Heroku + + ```bash + heroku login + heroku create + heroku buildpacks:add heroku/nodejs + heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static + git push heroku master + ``` + +Подробная информация: [Начало работы с SPA на Heroku](https://gist.github.com/hone/24b06869b4c1eca701f9) ### Surge @@ -299,7 +405,7 @@ npm install --global surge 3. В проекте создайте `deploy.sh` с указанным содержимым и запустите его для публикации: - ``` bash{13,20,23} + ```bash{13,20,23} #!/usr/bin/env sh # остановиться при ошибках @@ -319,3 +425,93 @@ npm install --global surge cd - ``` + +### Docker (Nginx) + +Опубликуйте ваше приложение, используя nginx внутри docker контейнера. + +1. Установите [docker](https://www.docker.com/get-started) + +2. Создайте файл `Dockerfile` в корневом каталоге проекта + + ```docker + FROM node:latest as build-stage + WORKDIR /app + COPY package*.json ./ + RUN npm install + COPY ./ . + RUN npm run build + + FROM nginx as production-stage + RUN mkdir /app + COPY --from=build-stage /app/dist /app + COPY nginx.conf /etc/nginx/nginx.conf + ``` + +3. Создайте файл `.dockerignore` в корневом каталоге проекта + + Настройка файла `.dockerignore` предотвращает копирование `node_modules` и любых промежуточных артефактов сборки в образ, что может вызывать проблемы при сборке. + + ``` + **/node_modules + **/dist + ``` + +4. Создайте файл `nginx.conf` в корневом каталоге проекта + + `Nginx` — это HTTP(s)-сервер, который будет работать в вашем контейнере docker. Он использует конфигурационный файл для определения того как предоставлять содержимое / какие порты прослушивать / и так далее. См. [документацию по конфигурации nginx](https://www.nginx.com/resources/wiki/start/topics/examples/full/) для ознакомления со всеми возможными примерами конфигураций. + + Ниже приведена простая конфигурация `nginx`, которая обслуживает ваш vue-проект на порту `80`. Корневой `index.html` служит для `page not found` / `404` ошибок, что позволяет использовать маршрутизации, основанной на `pushState()`. + + ```nginx + user nginx; + worker_processes 1; + error_log /var/log/nginx/error.log warn; + pid /var/run/nginx.pid; + events { + worker_connections 1024; + } + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + server { + listen 80; + server_name localhost; + location / { + root /app; + index index.html; + try_files $uri $uri/ /index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } + } + ``` + +5. Соберите образ docker + + ```bash + docker build . -t my-app + # Sending build context to Docker daemon 884.7kB + # ... + # Successfully built 4b00e5ee82ae + # Successfully tagged my-app:latest + ``` + +6. Запустите образ docker + + Эта сборка основана на официальном образе `nginx`, поэтому перенаправление логов уже настроено и само-демонтизация (self daemonizing) отключена. Для улучшения работы nginx в контейнере docker были установлены некоторые другие настройки по умолчанию. Подробнее см. в [репозитории nginx docker](https://hub.docker.com/_/nginx). + + ```bash + docker run -d -p 8080:80 my-app + curl localhost:8080 + # ... + ``` diff --git a/docs/ru/guide/html-and-static-assets.md b/docs/ru/guide/html-and-static-assets.md index c7d61fc1d6..22e9e6e5d7 100644 --- a/docs/ru/guide/html-and-static-assets.md +++ b/docs/ru/guide/html-and-static-assets.md @@ -16,7 +16,7 @@ В дополнение к [значениям по умолчанию, предоставляемым `html-webpack-plugin`](https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates), все [переменные окружения в клиентском коде](./mode-and-env.md#испоnьзование-переменных-окружения-в-кnиентском-коде) также доступны напрямую. Например, чтобы использовать значение `BASE_URL`: -``` html +```html ``` @@ -39,9 +39,13 @@ Эти подсказки внедряются [@vue/preload-webpack-plugin](https://github.com/vuejs/preload-webpack-plugin) и могут быть изменены / удалены с помощью `chainWebpack` через `config.plugin('prefetch')`. +::: tip Примечание для многостраничных конфигураций +При использовании многостраничной конфигурации имя плагина нужно изменить в соответствии со структурой `prefetch-{pagename}`, например `prefetch-app`. +::: + Например: -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { @@ -61,7 +65,7 @@ module.exports = { Когда prefetch плагин отключён, вы можете вручную указывать необходимые фрагменты для prefetch с помощью инлайновых комментариев для webpack: -``` js +```js import(/* webpackPrefetch: true */ './someAsyncComponent.vue') ``` @@ -75,7 +79,7 @@ Webpack добавит prefetch-ссылки когда родительский При использовании Vue CLI с существующим бэкендом, вам может потребоваться отключить генерацию `index.html`, чтобы сгенерированные ресурсы могли быть использованы с другим документом по умолчанию. Для этого добавьте в файл [`vue.config.js`](../config/#vue-config-js) следующее: -``` js +```js // vue.config.js module.exports = { // отключение хэшей в именах файлов @@ -115,29 +119,31 @@ module.exports = { Например, `url(./image.png)` будет преобразован в `require('./image.png')`, а тег шаблона -``` html +```html ``` будет скомпилирован в: -``` js +```js h('img', { attrs: { src: require('./image.png') }}) ``` -Внутри используется `file-loader` для определения конечного расположения файла с хэшем версии и правильный путь относительно корня, а также `url-loader` для инлайн-встраивания ресурсов, чей размер меньше 4 КБайт, чтобы уменьшить количество HTTP-запросов к серверу. +Внутри используется `file-loader` для определения конечного расположения файла с хэшем версии и правильный путь относительно корня, а также `url-loader` для инлайн-встраивания ресурсов, чей размер меньше 8 КБайт, чтобы уменьшить количество HTTP-запросов к серверу. -Изменить размер можно через [chainWebpack](../config/#chainwebpack). Например, чтобы установить лимит в 10 КБайт: +Изменить размер можно через [chainWebpack](../config/#chainwebpack). Например, чтобы установить лимит в 4 КБайт: -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { config.module .rule('images') - .use('url-loader') - .loader('url-loader') - .tap(options => Object.assign(options, { limit: 10240 })) + .set('parser', { + dataUrlCondition: { + maxSize: 4 * 1024 // 4KiB + } + }) } } ``` @@ -150,7 +156,7 @@ module.exports = { - Если URL начинается с `~`, то всё что после него будет интерпретироваться как запрос модуля. Это означает, что вы можете ссылаться на ресурсы даже внутри `node_modules`: - ``` html + ```html ``` @@ -170,13 +176,13 @@ module.exports = { - В `public/index.html` или других HTML-файлах, используемых `html-webpack-plugin` в качестве шаблонов, необходимо добавлять префикс в ссылки с помощью `<%= BASE_URL %>`: - ``` html + ```html ``` - В шаблонах потребуется сначала передать `BASE_URL` в компонент: - ``` js + ```js data () { return { publicPath: process.env.BASE_URL @@ -186,7 +192,7 @@ module.exports = { А затем использовать в шаблоне: - ``` html + ```html ``` diff --git a/docs/ru/guide/README.md b/docs/ru/guide/index.md similarity index 96% rename from docs/ru/guide/README.md rename to docs/ru/guide/index.md index 6fdb757620..29f17b2805 100644 --- a/docs/ru/guide/README.md +++ b/docs/ru/guide/index.md @@ -4,12 +4,6 @@ sidebarDepth: 0 # Введение - - -::: warning Предупреждение -Эта документация для `@vue/cli`. Для старой версии `vue-cli`, см. [здесь](https://github.com/vuejs/vue-cli/tree/v2#vue-cli--). -::: - Vue CLI — полноценная система для быстрой разработки на Vue.js, предоставляющая: - Интерактивное создание проекта через `@vue/cli`. diff --git a/docs/ru/guide/installation.md b/docs/ru/guide/installation.md index f9784b57a4..4e28152933 100644 --- a/docs/ru/guide/installation.md +++ b/docs/ru/guide/installation.md @@ -1,17 +1,12 @@ # Установка -::: danger Предупреждение о предыдущих версиях -Имя пакета изменилось с `vue-cli` на `@vue/cli`. -Если у вас установлена глобально предыдущая версия пакета `vue-cli` (1.x или 2.x), то необходимо сначала удалить её командой `npm uninstall vue-cli -g` или `yarn global remove vue-cli`. -::: - ::: tip Требования к версии Node -Vue CLI требуется [Node.js](https://nodejs.org/) версии 8.9 или выше (рекомендуется 8.11.0+). Управлять несколькими версиями Node на машине можно с помощью [nvm](https://github.com/creationix/nvm) или [nvm-windows](https://github.com/coreybutler/nvm-windows). +Vue CLI 4.x требуется [Node.js](https://nodejs.org/) версии 8.9 или выше (рекомендуется v10+). Управлять несколькими версиями Node на машине можно через [n](https://github.com/tj/n), [nvm](https://github.com/creationix/nvm) или [nvm-windows](https://github.com/coreybutler/nvm-windows). ::: -Для установки новых версий пакетов используйте одну из этих команд: +Для установки нового пакета используйте одну из следующих команд. Для их выполнения потребуются права администратора, если только npm не был установлен в системе через менеджер версий Node.js (например, n или nvm). -``` bash +```bash npm install -g @vue/cli # ИЛИ yarn global add @vue/cli @@ -24,3 +19,29 @@ yarn global add @vue/cli ```bash vue --version ``` + +### Обновление + +Для обновления глобального пакета Vue CLI выполните команду: + +```bash +npm update -g @vue/cli +# ИЛИ +yarn global upgrade --latest @vue/cli +``` + +#### Зависимости проекта + +Команды обновления, показанные выше, только для глобально установленного пакета Vue CLI. Для обновления одного или нескольких пакетов, связанных с `@vue/cli` (включая пакеты, начинающиеся с `@vue/cli-plugin-` или `vue-cli-plugin-`) внутри проекта, запустите `vue upgrade` в каталоге проекта: + +``` +Использование: upgrade [options] [plugin-name] +(экспериментально) upgrade vue cli service / plugins +Опции: + -t, --to Обновить до определённой версии + -f, --from Пропустить проверку установленного плагина, предполагая что он будет обновляться с указанной версии + -r, --registry Использовать указанный npm-регистр при установке зависимостей + --all Обновить все плагины + --next Также проверять на наличие alpha / beta / rc версий при обновлении + -h, --help Вывести информацию об использовании команды +``` diff --git a/docs/ru/guide/mode-and-env.md b/docs/ru/guide/mode-and-env.md index c6a8576674..2fbde776ba 100644 --- a/docs/ru/guide/mode-and-env.md +++ b/docs/ru/guide/mode-and-env.md @@ -1,8 +1,38 @@ -# Переменные окружения и режимы работы +# Режимы работы и переменные окружения -Вы можете указать переменные окружения в специальных файлах в корне вашего проекта: +## Режимы работы + +**Режимы работы** — важная часть проектов Vue CLI. По умолчанию есть три режима работы: + +- `development` используется `vue-cli-service serve` +- `test` используется `vue-cli-service test:unit` +- `production` используется `vue-cli-service build` и `vue-cli-service test:e2e` + +Можно переопределять используемый для команды режим по умолчанию опцией `--mode`. Например, если необходимо использовать переменные для разработки в команде сборки: + +``` +vue-cli-service build --mode development +``` + +При запуске `vue-cli-service` загрузит переменные окружения из всех [соответствующих файлов](#переменные-окружения). Если в них не указана переменная `NODE_ENV`, то она установится соответствующим образом. Например, `NODE_ENV` будет установлена в `"production"` в режиме production, `"test"` в режиме test, а по умолчанию `"development"` в противном случае. + +Затем `NODE_ENV` определяет основной режим работы приложения — разработка, production или тестирование — и, следовательно, какую конфигурацию webpack требуется создавать. + +Например, для `NODE_ENV=test` Vue CLI создаёт конфигурацию webpack, которая оптимизирована и предназначена для модульных тестов. В ней не обрабатываются изображения и другие ресурсы, которые не требуются для модульных тестов. + +Аналогично, при `NODE_ENV=development` создаётся конфигурация webpack, которая включает горячую перезагрузку модулей (HMR), не добавляет хэши в имена файлов ресурсов и не создаёт сборку для вендоров, чтобы обеспечить быструю пересборку при запуске сервера разработки. + +При запуске `vue-cli-service build`, значение всегда должно быть `NODE_ENV=production` для получения приложения готового к публикации, независимо от окружения куда будет осуществляться публиковаться. + +::: warning Предупреждение об использовании NODE_ENV +Если в вашем окружении по умолчанию установлен `NODE_ENV`, необходимо либо удалить его, либо явно установить `NODE_ENV` при запуске команд `vue-cli-service`. +::: + +## Переменные окружения + +Переменные окружения можно указать в специальных файлах в корне проекта: -``` bash +```bash .env # загружается во всех случаях .env.local # загружается во всех случаях, игнорируется git .env.[mode] # загружается только в указанном режиме работы @@ -13,40 +43,36 @@ ``` FOO=bar -VUE_APP_SECRET=secret +VUE_APP_NOT_SECRET_CODE=some_value ``` -Эти переменные будут доступны для всех команд `vue-cli-service`, плагинов и зависимостей. +::: warning ВНИМАНИЕ +Не храните никаких секретов (например, приватных ключей API) в приложении! -::: tip Приоритет загрузки переменных окружения -Файл с переменными для определённого режима работы (например, `.env.production`) имеет более высокий приоритет, чем общий файл (например, `.env`). - -Кроме того, переменные окружения, которые уже существуют при загрузке Vue CLI имеют наивысший приоритет и не будут перезаписаны значениями из файлов `.env`. -::: - -::: warning Предупреждение об использовании NODE_ENV -Если в вашем окружении по умолчанию установлен `NODE_ENV`, вы должны либо удалить его, либо явно установить `NODE_ENV` при выполнении команд `vue-cli-service`. +Так как переменные окружения внедряются в сборку, то любой желающий сможет увидеть их, изучив файлы сборки приложения. ::: -## Режимы работы +Обратите внимание, что только `NODE_ENV`, `BASE_URL` и переменные, именованные с префикса `VUE_APP_`, статически внедрятся в *клиентскую сборку* с помощью `webpack.DefinePlugin`. Это сделано во избежание случайного обнародования закрытого ключа на машине, которая может иметь такое же имя. -**Режимы работы** — важная часть проектов Vue CLI. По умолчанию, есть три режима работы: +Подробнее о правилах парсинга env [в документации `dotenv`](https://github.com/motdotla/dotenv#rules). Можно также использовать [dotenv-expand](https://github.com/motdotla/dotenv-expand) для переменных расширения (доступно с версии Vue CLI 3.5+). Например: -- `development` используется `vue-cli-service serve` -- `production` используется `vue-cli-service build` и `vue-cli-service test:e2e` -- `test` используется `vue-cli-service test:unit` +```bash +FOO=foo +BAR=bar +CONCAT=$FOO$BAR # CONCAT=foobar +``` -Обратите внимание, что режим работы отличается от `NODE_ENV`, поскольку режим может устанавливать несколько переменных окружения. Тем не менее, каждый режим устанавливает `NODE_ENV` в такое же значение по умолчанию — например, `NODE_ENV` будет установлен в `"development"` в режиме разработки. +Загруженные переменные станут доступны всем командам `vue-cli-service`, плагинам и зависимостям. -Вы можете установить переменные окружения только для определённого режима работы с помощью постфикса `.env` файла. Например, если создать файл с именем `.env.development` в корне вашего проекта, тогда переменные объявленные в нём будут загружаться только в режиме development. +::: tip Приоритет загрузки переменных окружения +Файл с переменными для определённого режима работы (например, `.env.production`) имеет более высокий приоритет, чем общий файл (например, `.env`). -Вы можете переопределить режим по умолчанию для команды, с помощью опции `--mode`. Например, если необходимо использовать переменные для разработки в команде сборки, добавьте это в свои скрипты `package.json`: +Кроме того, переменные окружения, которые уже существуют при запуске Vue CLI имеют наивысший приоритет и не будут перезаписаны значениями из файлов `.env`. -``` -"dev-build": "vue-cli-service build --mode development", -``` +Файлы `.env` загружаются при запуске `vue-cli-service`. При внесении изменений в файлы необходимо перезапустить службу. +::: -## Пример: режим Staging +### Пример: режим Staging Предположим, что у нас есть приложение с `.env` файлом: @@ -58,26 +84,26 @@ VUE_APP_TITLE=My App ``` NODE_ENV=production -VUE_APP_TITLE=My App (staging) +VUE_APP_TITLE=My Staging App ``` -- `vue-cli-service build` собирает приложение для production, загружает `.env`, `.env.production` и `.env.production.local` если они присутствуют; +- `vue-cli-service build` собирает приложение для production, загружает `.env`, `.env.production` и `.env.production.local` если они существуют; -- `vue-cli-service build --mode staging` собирает приложение для production в режиме staging, используя `.env`, `.env.staging` и `.env.staging.local` если они присутствуют. +- `vue-cli-service build --mode staging` собирает приложение для production в режиме staging, используя `.env`, `.env.staging` и `.env.staging.local` если они существуют. В обоих случаях приложение собирается для production из-за установленного `NODE_ENV`, но в режиме staging, `process.env.VUE_APP_TITLE` будет перезаписываться другим значением. -## Использование переменных окружения в клиентском коде +### Использование переменных окружения в клиентском коде -Только переменные с префиксом `VUE_APP_` статически внедряются в клиентскую сборку используя `webpack.DefinePlugin`. К ним можно получить доступ из кода вашего приложения: +Можно получить доступ к переменным окружения из кода приложения: -``` js -console.log(process.env.VUE_APP_SECRET) +```js +console.log(process.env.VUE_APP_NOT_SECRET_CODE) ``` -На этапе сборки `process.env.VUE_APP_SECRET` будет заменяться соответствующим значением. Когда в файле указано `VUE_APP_SECRET=secret` — после сборки значением будет `"secret"`. +На этапе сборки `process.env.VUE_APP_NOT_SECRET_CODE` будет заменяться соответствующим значением. Когда в файле указано `VUE_APP_NOT_SECRET_CODE=some_value` — после сборки значением будет `"some_value"`. -В дополнение к переменным `VUE_APP_*` также есть две специальные переменные, которые всегда доступны в коде вашего приложения: +В дополнение к переменным `VUE_APP_*` есть также две специальные переменные, которые всегда доступны в коде приложения: - `NODE_ENV` — значение будет `"development"`, `"production"` или `"test"` в зависимости от [режима работы](#режимы-работы) в котором работает приложение. - `BASE_URL` — соответствует опции `publicPath` в `vue.config.js` и определяет базовый путь по которому опубликовано ваше приложение. @@ -85,11 +111,19 @@ console.log(process.env.VUE_APP_SECRET) Все разрешённые переменные окружения также будут доступны внутри `public/index.html` как обсуждалось ранее в разделе [HTML - Интерполяции](./html-and-static-assets.md#интерпоnяции). ::: tip Совет -Можно добавлять вычисляемые переменные окружения в `vue.config.js`. Они по-прежнему должны именоваться с префикса `VUE_APP_`. Это может пригодиться например для получения информации о версии `process.env.VUE_APP_VERSION = require('./package.json').version` +Можно добавлять вычисляемые переменные окружения в `vue.config.js`. Они по-прежнему должны именоваться с префикса `VUE_APP_`. Может пригодиться для получения информации о версии + +```js +process.env.VUE_APP_VERSION = require('./package.json').version + +module.exports = { + // конфигурация +} +``` ::: -## Переменные только для локального окружения +### Переменные только для локального окружения -Иногда необходимы переменные окружения, которые не должны быть привязаны к кодовой базе, особенно если ваш проект размещается в публичном репозитории. В таком случае вы должны использовать файл `.env.local`. Локальные env-файлы добавлены в `.gitignore` по умолчанию. +Иногда необходимы переменные окружения, которые не должны быть привязаны к кодовой базе, особенно если проект размещается в публичном репозитории. В таком случае следует использовать файл `.env.local`. Локальные env-файлы добавлены в `.gitignore` по умолчанию. `.local` также могут добавляться к env-файлам специфичным для режима работы, например `.env.development.local` будет загружен во время разработки, и будет игнорироваться git. diff --git a/docs/ru/guide/plugins-and-presets.md b/docs/ru/guide/plugins-and-presets.md index 93ea96960e..425f089dca 100644 --- a/docs/ru/guide/plugins-and-presets.md +++ b/docs/ru/guide/plugins-and-presets.md @@ -2,7 +2,7 @@ ## Плагины -Архитектура Vue CLI основана на плагинах. Если изучить `package.json` в свежесозданном проекте, то вы найдёте зависимости, имена которых начинаются с `@vue/cli-plugin-`. Такие плагины могут изменять внутреннюю конфигурацию webpack и внедрять команды в `vue-cli-service`. Большинство возможностей, упоминаемых при создании проекта, реализованы ими. +Vue CLI использует архитектуру на основе плагинов. Если изучить `package.json` в только что созданном проекте, можно обнаружить зависимости, которые начинаются с `@vue/cli-plugin-`. Плагины могут модифицировать внутреннюю конфигурацию webpack и внедрять команды в `vue-cli-service`. Большинство возможностей, перечисленных в процессе создания проекта, реализованы в виде плагинов. Основанная на плагинах архитектура позволяет Vue CLI оставаться гибкой и расширяемой. Если вы хотите создать собственный плагин — изучите [руководство по созданию плагинов](../dev-guide/plugin-dev.md). @@ -14,7 +14,7 @@ Каждый плагин для CLI поставляется с генератором (который создаёт файлы) и плагином для runtime (который меняет конфигурацию webpack и внедряет команды). Когда вы используете `vue create` для создания нового проекта, некоторые плагины будут уже предустановлены, в зависимости от вашего выбора необходимых возможностей. В случае, когда необходимо установить плагин в уже существующий проект, вы должны сделать это командой `vue add`: -``` bash +```bash vue add eslint ``` @@ -28,27 +28,27 @@ vue add eslint Команда `@vue/eslint` трансформируется в полное название пакета `@vue/cli-plugin-eslint`, устанавливает его из npm, и запускает его генератор. -``` bash +```bash # это аналогично предыдущей команде vue add cli-plugin-eslint ``` Без префикса `@vue` команда будет трансформировать название к публичному пакету. Например, чтобы установить сторонний плагин `vue-cli-plugin-apollo`: -``` bash +```bash # устанавливает и запускает vue-cli-plugin-apollo vue add apollo ``` Вы можете также использовать сторонние плагины со специфичным scope. Например, если плагин назван `@foo/vue-cli-plugin-bar`, то его можно добавить командой: -``` bash +```bash vue add @foo/bar ``` Можно передавать опции генерации в установленный плагин (для пропуска интерактивного выбора): -``` bash +```bash vue add eslint --config airbnb --lintOn save ``` @@ -102,7 +102,7 @@ vue add eslint --config airbnb --lintOn save Вот пример пресета настроек: -``` json +```json { "useConfigFiles": true, "cssPreprocessor": "sass", @@ -120,7 +120,7 @@ vue add eslint --config airbnb --lintOn save Информация из пресета настроек используется генераторами плагинов для создания в проекте соответствующих файлов. Кроме того, в дополнении к полям выше, возможно добавление дополнительных настроек для встроенных инструментов: -``` json +```json { "useConfigFiles": true, "plugins": {...}, @@ -139,7 +139,7 @@ vue add eslint --config airbnb --lintOn save Вы можете явно указать версии используемых плагинов: -``` json +```json { "plugins": { "@vue/cli-plugin-eslint": { @@ -160,7 +160,7 @@ vue add eslint --config airbnb --lintOn save Для таких случаев можно указать `"prompts": true` в настройках плагина, чтобы позволить пользователю сделать свой выбор: -``` json +```json { "plugins": { "@vue/cli-plugin-eslint": { @@ -181,23 +181,27 @@ vue add eslint --config airbnb --lintOn save После публикации репозитория, при создании проекта теперь можно указать опцию `--preset` для использования пресета из удалённого репозитория: -``` bash +```bash # использование пресета из репозитория GitHub vue create --preset username/repo my-project ``` GitLab и BitBucket также поддерживаются. Убедитесь, что используете опцию `--clone` при загрузке из стороннего репозитория: -``` bash +```bash vue create --preset gitlab:username/repo --clone my-project vue create --preset bitbucket:username/repo --clone my-project + +# репозитории на собственном хостинге +vue create --preset gitlab:my-gitlab-server.com:group/projectname --clone my-project +vue create --preset direct:ssh://git@my-gitlab-server.com/group/projectname.git --clone my-project ``` ### Пресет из локального файла При разработке удалённого пресета настроек может часто требоваться отправлять пресет в удалённый репозиторий для его проверки. Для упрощения разработки можно использовать локальные пресеты напрямую. Vue CLI будет загружать локальные пресеты, если путь в значении `--preset` будет относительным или абсолютным, или заканчиваться на `.json`: -``` bash +```bash # ./my-preset должен быть каталогом, содержащим preset.json vue create --preset ./my-preset my-project diff --git a/docs/ru/guide/prototyping.md b/docs/ru/guide/prototyping.md index 21683e8171..553a46c603 100644 --- a/docs/ru/guide/prototyping.md +++ b/docs/ru/guide/prototyping.md @@ -1,9 +1,11 @@ # Мгновенное прототипирование -Вы можете быстро создавать прототип в одном файле `*.vue` с помощью команд `vue serve` и `vue build`, но для них сначала потребуется глобально установить дополнительный плагин: +Вы можете быстро создавать прототип в одном файле `*.vue` с помощью команд `vue serve` и `vue build`, но для них сначала потребуется глобально установить плагин в дополнение к Vue CLI: -``` bash -npm install -g @vue/cli-service-global +```bash +npm install -g @vue/cli @vue/cli-service-global +# или +yarn global add @vue/cli @vue/cli-service-global ``` Недостаток `vue serve` в том, что он полагается на глобально установленные зависимости, которые могут отличаться на разных машинах. Поэтому его рекомендуется использовать только для быстрого прототипирования. @@ -18,14 +20,15 @@ npm install -g @vue/cli-service-global Опции: - -o, --open Открыть в браузере - -c, --copy Скопировать локальный URL в буфер обмена - -h, --help Вывести информацию об использовании команды + -o, --open Открыть в браузере + -c, --copy Скопировать локальный URL в буфер обмена + -p, --port Используемый сервером порт (по умолчанию: 8080 или следующий свободный порт) + -h, --help Вывести информацию об использовании команды ``` Всё что вам потребуется — файл `App.vue`: -``` vue +```vue @@ -33,13 +36,13 @@ npm install -g @vue/cli-service-global Затем, в каталоге с файлом `App.vue`, выполните команду: -``` bash +```bash vue serve ``` `vue serve` использует такую же конфигурацию по умолчанию (webpack, babel, postcss & eslint) как и проекты создаваемые с помощью `vue create`. Он автоматически выбирает стартовый файл в текущем каталоге — этот файл может быть одним из `main.js`, `index.js`, `App.vue` или `app.vue`. Можно также явно указать стартовый файл: -``` bash +```bash vue serve MyComponent.vue ``` @@ -62,7 +65,7 @@ vue serve MyComponent.vue Вы можете собрать целевой файл в режиме production для публикации с помощью `vue build`: -``` bash +```bash vue build MyComponent.vue ``` diff --git a/docs/ru/guide/troubleshooting.md b/docs/ru/guide/troubleshooting.md new file mode 100644 index 0000000000..4e73a20b30 --- /dev/null +++ b/docs/ru/guide/troubleshooting.md @@ -0,0 +1,34 @@ +# Поиск и устранение неисправностей + +В это документе рассматриваются некоторые общие проблемы, касающиеся Vue CLI, и способы их решения. Прежде чем открывать новый issue, всегда выполняйте следующие действия. + +## Запуск установки через `sudo` или как `root` + +Если устанавливаете `@vue/cli-service` как пользователь `root` или с помощью `sudo`, то могут возникнуть проблемы при запуске скриптов `postinstall` пакета. + +Это функция безопасности npm. Вы всегда должны избегать запуска npm с привилегиями root, потому что сценарии установки скриптов могут быть непреднамеренно вредоносными. + +Однако, если необходимо, то можно обойти эту ошибку, установив флаг `--unsafe-perm` для npm. Это реализуется путём добавления префикса с переменной окружения к команде: + +```bash +npm_config_unsafe_perm=true vue create my-project +``` + +## Символические ссылки в `node_modules` + +Если есть зависимости, установленные через `npm link` или `yarn link`, ESLint (а иногда и Babel) могут работать некорректно для этих слинкованных зависимостей. Это происходит потому, что [по умолчанию webpack разрешает символические ссылки на их настоящее местоположение](https://webpack.js.org/configuration/resolve/#resolvesymlinks), таким образом ломая поиск конфигурации ESLint / Babel. + +Обходным решением этой проблемы будет отключение вручную разрешения символических ссылок в webpack: + +```js +// vue.config.js +module.exports = { + chainWebpack: (config) => { + config.resolve.symlinks(false) + } +} +``` + +::: warning ПРЕДУПРЕЖДЕНИЕ +Отключение `resolve.symlinks` может сломать горячую перезагрузку модулей, если ваши зависимости устанавливались сторонними npm-клиентами, использующие символические ссылки, такие как `cnpm` или `pnpm`. +::: diff --git a/docs/ru/guide/webpack.md b/docs/ru/guide/webpack.md index 9151be65cb..7132e357fa 100644 --- a/docs/ru/guide/webpack.md +++ b/docs/ru/guide/webpack.md @@ -4,7 +4,7 @@ Самый простой способ изменять конфигурацию webpack — использовать объект в опции `configureWebpack` в файле `vue.config.js`: -``` js +```js // vue.config.js module.exports = { configureWebpack: { @@ -23,7 +23,7 @@ module.exports = { Если необходимо условное поведение, в зависимости от окружения, или вы хотите напрямую изменять конфигурацию — используйте функцию (будет лениво выполняться после установки переменных окружения). Она получает итоговую конфигурацию в качестве аргумента. Внутри функции можно напрямую изменить конфигурацию, ИЛИ вернуть объект для объединения: -``` js +```js // vue.config.js module.exports = { configureWebpack: config => { @@ -48,14 +48,13 @@ module.exports = { ### Изменение настроек загрузчика -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { config.module .rule('vue') .use('vue-loader') - .loader('vue-loader') .tap(options => { // изменение настроек... return options @@ -70,7 +69,7 @@ module.exports = { ### Добавление нового загрузчика -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { @@ -81,6 +80,10 @@ module.exports = { .use('graphql-tag/loader') .loader('graphql-tag/loader') .end() + // Добавление ещё одного загрузчика + .use('other-loader') + .loader('other-loader') + .end() } } ``` @@ -89,7 +92,7 @@ module.exports = { Если вы хотите заменить существующий [базовый загрузчик](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-service/lib/config/base.js), например воспользоваться `vue-svg-loader` для вставки SVG-файлов инлайн вместо загрузки обычными файлами: -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { @@ -110,7 +113,7 @@ module.exports = { ### Изменение настроек плагина -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { @@ -127,7 +130,7 @@ module.exports = { Например, предположим, необходимо изменить местоположение `index.html` по умолчанию с `/Users/test/proj/public/index.html` на `/Users/test/proj/app/templates/index.html`. По ссылке [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin#options) перечислен список параметров, которые можем передавать. Чтобы изменить шаблон, передадим новый путь к шаблону следующей конфигурацией: -``` js +```js // vue.config.js module.exports = { chainWebpack: config => { @@ -153,29 +156,35 @@ module.exports = { Вы можете перенаправить вывод в файл для более удобного изучения: -``` bash +```bash vue inspect > output.js ``` +По умолчанию команда `inspect` показывает конфигурацию для разработки. Для отображения конфигурации для production необходимо запустить: + +```bash +vue inspect --mode production > output.prod.js +``` + Обратите внимание, что вывод не является файлом рабочей конфигурации webpack, это только сериализованный формат предназначенный для проверки. Вы также можете указать подмножество конфигурации для проверки, указав путь: -``` bash +```bash # показать только первое правило vue inspect module.rules.0 ``` Или указать именованное правило или плагин: -``` bash +```bash vue inspect --rule vue vue inspect --plugin html ``` Наконец, вы можете вывести все именованные правила и плагины: -``` bash +```bash vue inspect --rules vue inspect --plugins ``` diff --git a/docs/ru/README.md b/docs/ru/index.md similarity index 96% rename from docs/ru/README.md rename to docs/ru/index.md index 1815f27fc0..90c66711f4 100644 --- a/docs/ru/README.md +++ b/docs/ru/index.md @@ -6,10 +6,6 @@ actionLink: /ru/guide/ footer: MIT Licensed | Copyright © 2018-present Evan You --- -
- -
-

Богатые возможности

@@ -41,7 +37,7 @@ footer: MIT Licensed | Copyright © 2018-present Evan You Установка: -``` bash +```bash npm install -g @vue/cli # ИЛИ yarn global add @vue/cli @@ -49,7 +45,7 @@ yarn global add @vue/cli Создание проекта: -``` bash +```bash vue create my-project # ИЛИ vue ui diff --git a/docs/ru/migrating-from-v3/index.md b/docs/ru/migrating-from-v3/index.md new file mode 100644 index 0000000000..855b84307c --- /dev/null +++ b/docs/ru/migrating-from-v3/index.md @@ -0,0 +1,299 @@ +--- +sidebar: auto +--- + +# Миграция с версии v3 + +Для начала глобально установите последнюю версию Vue CLI: + +```bash +npm install -g @vue/cli +# ИЛИ +yarn global add @vue/cli +``` + +## Обновление всех плагинов сразу + +В существующих проектах запустите команду: + +```bash +vue upgrade +``` + +После чего ознакомьтесь со следующим разделом с информацией о крупных изменениях (breaking changes) в каждом пакете. + +------ + +## Миграция вручную по одному пакету + +При желании выполнить миграцию постепенно и вручную несколько советов: + +### Глобальный пакет `@vue/cli` + +#### [Переработана команда](https://github.com/vuejs/vue-cli/pull/4090) `vue upgrade` + +- Было: `vue upgrade [patch | minor | major]` — выполняла только установку последних версий плагинов Vue CLI. +- Стало: `vue upgrade [plugin-name]` — кроме обновления плагинов, запускает миграции из них для автоматизации процесса обновления. Для получения информации о дополнительных опциях этой команды выполните `vue upgrade --help`. + +#### Изменён формат вывода `vue --version` + +При запуске `vue --version`: + +- 3.x: выводит `3.12.0` +- 4.x: выводит `@vue/cli 4.0.0` + +#### Добавлен дополнительный шаг подтверждения во избежание перезаписи + +При запуске `vue invoke` / `vue add` / `vue upgrade` теперь появляется [дополнительный шаг подтверждения](https://github.com/vuejs/vue-cli/pull/4275) при наличии незафиксированных изменений в текущем репозитории. + +![image](https://user-images.githubusercontent.com/3277634/65588457-23db5a80-dfba-11e9-9899-9dd72efc111e.png) + +#### Vue Router и Vuex теперь имеют сопутствующие CLI-плагины + +При запуске `vue add vuex` или `vue add router`: + +- В версии 3, только `vuex` или `vue-router` добавляется в проект; +- В версии 4, также устанавливается `@vue/cli-plugin-vuex` или `@vue/cli-plugin-router`. + +В настоящее время это не привносит ничего особенного для конечных пользователей, но такой подход позволяет добавлять больше возможностей для пользователей Vuex и Vue Router позднее. + +Для разработчиков пресетов и плагинов есть ещё несколько изменений в этих двух плагинах: + +- Структура каталогов по умолчанию изменена: + - `src/store.js` перемещён в `src/store/index.js`; + - `src/router.js` перемещён в `src/router/index.js`; +- Опции `router` и `routerHistoryMode` в файле `preset.json` по-прежнему поддерживаются для совместимости. Но рекомендуется использовать `plugins: { '@vue/cli-plugin-router': { historyMode: true } }` для консистентности. +- `api.hasPlugin('vue-router')` больше не поддерживается. Теперь `api.hasPlugin('router')`. + +### `@vue/cli-service` + +#### Обработка пробелов в шаблонах + +Во Vue CLI v3 для уменьшения размеров итоговой сборки по умолчанию отключена опция `preserveWhitespace` для `vue-template-compiler`. + +Однако это привносило свои тонкости использования. + +Но после релиза Vue 2.6 теперь можно управлять обработкой пробелов с помощью [новой опции `whitespace`](https://github.com/vuejs/vue/issues/9208#issuecomment-450012518). Поэтому во Vue CLI v4 перешли на использование этой новой опции по умолчанию. + +Возьмём в качестве примера следующий шаблон: + +```html +

+ Welcome to Vue.js world. + Have fun! +

+``` + +С опцией `preserveWhitespace: false` все пробелы между тегами будут удалены, поэтому он скомпилируется в: + +```html +

Welcome to Vue.jsworld. Have fun!

+``` + +С опцией `whitespace: 'condense'` он скомпилируется в: + +```html +

Welcome to Vue.js world. Have fun!

+``` + +Обратите внимание, что теперь сохраняется **инлайновый** пробел между тегами. + +#### `vue-cli-service build --mode development` + +Раньше при запуске команды `build` в режиме `development` расположение каталога `dist` отличалось от расположения в режиме `production`. Теперь, с учётом указанных ниже двух пулл-реквестов, структура и расположение каталогов будет во всех режимах одинакова (имена файлов всё ещё различаются — никаких хэшей в режиме `development`): + +- [#4323](https://github.com/vuejs/vue-cli/pull/4323) ensure consistent directory structure for all modes +- [#4302](https://github.com/vuejs/vue-cli/pull/4302) move dev configs into serve command + +#### Для пользователей SASS/SCSS + +Раньше во Vue CLI v3 использовался `sass-loader@7` по умолчанию. + +Недавно вышел `sass-loader@8` в котором довольно сильно изменился формат конфигурации. Примечания к релизу: + +`@vue/cli-service` продолжает поддерживать `sass-loader@7` в v4, но настоятельно рекомендуем обратить внимание на релиз `sass-loader@8` и обновиться до последней версии. + +#### Для пользователей Less + +`less-loader` v4 несовместим с `less` >= v3.10, см. . +Настоятельно рекомендуем обновиться до `less-loader@5`, если в проекте используется Less. + +#### Для пользователей CSS модулей + +- [Устаревшая опция `css.modules` заменена на `css.requireModuleExtension`](https://github.com/vuejs/vue-cli/pull/4387). Это связано с обновлением `css-loader` до v3 и изменением формата конфигурации. С подробным объяснением можно ознакомиться по ссылке. + +#### Настройки `vue.config.js` + +Уже объявленная как устаревшая [опция `baseUrl`](../config/#baseurl) теперь [удалена](https://github.com/vuejs/vue-cli/pull/4388). + +#### `chainWebpack` / `configureWebpack` + +##### Метод `minimizer` в `chainWebpack` + +Если настраивали правила через `chainWebpack`, то обратите внимание, что `webpack-chain` обновлён с версии v4 до v6. Наиболее заметным изменением является конфигурация `minimizer`. + +Например, если необходимо включить опцию `drop_console` в плагине terser. +В версии v3 это можно сделать через `chainWebpack` так: + +```js +const TerserPlugin = require('terser-webpack-plugin') +module.exports = { + chainWebpack: (config) => { + config.optimization.minimizer([ + new TerserPlugin({ terserOptions: { compress: { drop_console: true } } }) + ]) + } +} +``` + +В версии v4 необходимо изменить таким образом: + +```js +module.exports = { + chainWebpack: (config) => { + config.optimization.minimizer('terser').tap((args) => { + args[0].terserOptions.compress.drop_console = true + return args + }) + } +} +``` + +##### Другие изменения + +- [Правило `pug-plain` переименовано в `pug-plain-loader`](https://github.com/vuejs/vue-cli/pull/4230) + +#### Базовые загрузчики / плагины + +Скорее всего это вряд ли повлияет на пользователей, если не настраивали опции через `chainWebpack` / `configureWebpack` + +`css-loader` был обновлён с версии v1 до v3: + +- [История изменений v2](https://github.com/webpack-contrib/css-loader/releases/tag/v2.0.0) +- [История изменений v3](https://github.com/webpack-contrib/css-loader/releases/tag/v3.0.0) + +Несколько базовых загрузчиков и плагинов webpack обновлены, с незначительными изменениями: + +- `url-loader` [с версии v1 до v2](https://github.com/webpack-contrib/url-loader/releases/tag/v2.0.0) +- `file-loader` [с версии v3 до v4](https://github.com/webpack-contrib/file-loader/releases/tag/v4.0.0) +- `copy-webpack-plugin` [с версии v4 до v5](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md#500-2019-02-20) +- `terser-webpack-plugin` [с версии v1 до v2](https://github.com/vuejs/vue-cli/pull/4676) + +### `@vue/cli-plugin-babel`, `@vue/babel-preset-app` + +#### core-js + +Требуется плагину babel в качестве peer-зависимости для полифилов, используемых в транспилированном коде. + +Во Vue CLI v3 использовалась `core-js` версии 2.x, теперь она обновлена до 3.x. + +Эта миграция автоматизирована, достаточно выполнить команду `vue upgrade babel`. Но если добавлялись пользовательские полифилы, может потребоваться обновить имена полифилов (подробную информацию можно найти в [истории изменений core-js](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md#L279-L297)). + +#### Пресет Babel + +Эта миграция также автоматизирована, при обновлении командой `vue upgrade babel`. + +- В версии v3, babel пресет по умолчанию в `babel.config.js` был `@vue/app`. +- В версии v4, пресет перемещён в плагин и теперь называется `@vue/cli-plugin-babel/preset` + +Необходимость этого в том, что `@vue/babel-preset-app` в действительности является косвенной зависимостью проекта. Это работает благодаря «поднятию» (hoisting) npm-пакета. Однако может стать причиной потенциальных проблем, если у проекта несколько косвенных зависимостей одного и того же пакета, или если менеджер пакетов накладывает более строгие ограничения при разрешении зависимостей (например, yarn plug'n'play или pnpm). Поэтому он вынесен отдельной зависимостью проекта (`@vue/cli-plugin-babel`) для большей совместимости со стандартами и меньшей подверженности ошибкам. + +------ + +### `@vue/cli-plugin-eslint` + +Плагин теперь [требует ESLint в качестве peer-зависимости](https://github.com/vuejs/vue-cli/pull/3852). + +Это не повлияет на проекты, созданные с помощью Vue CLI 3.1 или более поздних версий. + +Если проект был создан с помощью Vue CLI 3.0.x или более ранних версий, то потребуется добавить `eslint@4` к зависимостям проекта (это автоматизированно при обновлении плагина с помощью команды `vue upgrade eslint`). + +Также рекомендуется обновить ESLint до версии v5, а конфигурацию ESLint до последней версии (поддержка ESLint v6 будет добавлена в ближайшем будущем). + +------ + +#### Пресет Prettier + +Старая реализация пресета prettier была несовершенной. Шаблон по умолчанию обновлён с версии Vue CLI v3.10. + +Теперь требуются `eslint`, `eslint-plugin-prettier` и `prettier` в качестве peer-зависимостей, следуя [стандартным практикам экосистемы ESLint](https://github.com/eslint/eslint/issues/3458). + +В старых проектах при возникновении проблем как `Cannot find module: eslint-plugin-prettier` необходимо выполнить следующую команду для их исправления: + +```bash +npm install --save-dev eslint@5 @vue/eslint-config-prettier@5 eslint-plugin-prettier prettier +``` + +------ + +#### Настройки `lintOnSave` + +(затрагивает только процесс разработки) + +Значение по умолчанию для опции `lintOnSave` (если не было указано) [изменено с `true` на `'default'`](https://github.com/vuejs/vue-cli/pull/3975). Ознакомиться с подробным объяснением можно [в документации](../config/#lintonsave). + +Вкратце: + +- В версии v3, по умолчанию, предупреждения линтинга и ошибки отображаются в браузере в слое для ошибок поверх приложения. +- В версии v4, по умолчанию, только ошибки линтинга будут таким образом прерывать процесс разработки. Предупреждения будут отображаться в консоли терминала. + +### `@vue/cli-plugin-pwa` + +Базовый плагин workbox-webpack-plugin обновлён с версии v3 до v4. См. [примечания к релизу](https://github.com/GoogleChrome/workbox/releases/tag/v4.0.0). + +Теперь доступно поле `pwa.manifestOptions` (его можно указать в файле `vue.config.js`). Благодаря этой опции можно сгенерировать `manifest.json` из объекта конфигурации, а не копировать из каталога `public`. Это обеспечивает более консистентный интерфейс управления конфигурацией PWA (Обратите внимание, что это опциональная возможность. Связанные пулл-реквесты: [#2981](https://github.com/vuejs/vue-cli/pull/2981), [#4664](https://github.com/vuejs/vue-cli/pull/4664)). + +### `@vue/cli-plugin-e2e-cypress` + +До Vue CLI v3.0.0-beta.10 команда для E2E-тестирования по умолчанию была `vue-cli-service e2e`. Позднее изменена на `vue-cli-service test:e2e`. Предыдущая команда объявлена устаревшей, но всё ещё поддерживалась. Теперь [поддержка старой команды удалена](https://github.com/vuejs/vue-cli/pull/3774). + +### `@vue/cli-plugin-e2e-nightwatch` + +Nightwatch.js обновлён с версии 0.9 до 1.x. Рекомендуем сначала изучить [руководство по миграции Nightwatch](https://github.com/nightwatchjs/nightwatch/wiki/Migrating-to-Nightwatch-1.0). + +Поставляемая в комплекте конфигурация и генерируемые тесты [были полностью переработаны](https://github.com/vuejs/vue-cli/pull/4541). Перейдите по ссылке для получения более подробной информации. Большинство используемых кейсов во Vue CLI v3 по-прежнему поддерживаются. Это просто добавление новых возможностей. + +Поскольку ChromeDriver изменил свою стратегию версионирования с 73-й версии, теперь он сделан peer-зависимостью проекта. В плагине реализована простая проверка версии браузера, поэтому при обновлении до несовместимой версии Chrome появится предупреждение с предложением обновить до соответствующей версии и ChromeDriver. + +------ + +Аналогично плагину для cypress, поддержка устаревшей команды `vue-cli-service e2e` удалена. + +### `@vue/cli-plugin-typescript` + +При импорте файла без расширения, настройки webpack по разрешению модулей теперь [отдают предпочтение файлам с расширениями `ts(x)` вместо `js(x)` и `.vue`](https://github.com/vuejs/vue-cli/pull/3909). Настоятельно рекомендуется всегда указывать расширение файла при импорте `.vue` файлов. + +### `@vue/cli-plugin-unit-jest` + +Обновлён Jest с версии v23 до v24, поэтому рекомендуем сначала изучить [примечания к релизу](https://jestjs.io/blog/2019/01/25/jest-24-refreshing-polished-typescript-friendly). А также, при необходимости, ознакомиться с [полной историей изменений](https://github.com/facebook/jest/blob/20ba4be9499d50ed0c9231b86d4a64ec8a6bd303/CHANGELOG.md#user-content-2400). + +Плагин `unit-jest` теперь поставляется с 4 пресетами конфигурации: + +- `@vue/cli-plugin-unit-jest` — пресет по умолчанию для наиболее распространённых типов проектов +- `@vue/cli-plugin-unit-jest/presets/no-babel` — если не установлен `@vue/cli-plugin-babel` и требуется не использовать babel в проекте +- `@vue/cli-plugin-unit-jest/presets/typescript` — пресет с поддержкой TypeScript (но без поддержки TSX) +- `@vue/cli-plugin-unit-jest/presets/typescript-and-babel` — пресет с поддержкой TypeScript (в том числе TSX) и babel. + +Если после создания проекта стандартная конфигурация Jest не изменялась (расположена в файле `jest.config.js` или в поле `jest` в `package.json`), то можно просто заменить массивный объект конфигурации одним единственным полем: + +```js +module.exports = { + // Замените имя пресета на одно из списка выше по необходимости + preset: '@vue/cli-plugin-unit-jest' +} +``` + +(зависимости `ts-jest`, `babel-jest` можно удалить после миграции конфигурации на использование пресета) + +::: tip Напоминание +По умолчанию тестовое окружение в новых пресетах использует jsdom@15, что отличается от среды по умолчанию в Jest 24 (jsdom@11). Это должно быть согласовано в предстоящем обновлении Jest 25. Большинство пользователей не будут затронуты этим изменением. Подробную информацию, связанную с jsdom, можно найти в истории изменений +::: + +### `@vue/cli-plugin-unit-mocha` + +- Теперь используется mochapack вместо mocha-webpack, см. историю изменений . Это изменение вряд ли повлияет на фактическое использование. +- Обновление mocha до версии 6, см. [историю изменений Mocha](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md#600-0--2019-01-01) для подробной информации. + +### `@vue/cli-service-global` + +См. подробные изменения в пакетах [`@vue/cli-service`](#vue-cli-service) и [`@vue/cli-plugin-eslint`](#vue-cli-plugin-eslint). diff --git a/docs/zh/README.md b/docs/zh/README.md deleted file mode 100644 index 27b3fc7223..0000000000 --- a/docs/zh/README.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -home: true -heroImage: /favicon.png -actionText: 起步 → -actionLink: /zh/guide/ -features: -- title: 功能丰富 - details: 对 Babel、TypeScript、ESLint、PostCSS、PWA、单元测试和 End-to-end 测试提供开箱即用的支持。 -- title: 易于扩展 - details: 它的插件系统可以让社区根据常见需求构建和共享可复用的解决方案。 -- title: 无需 Eject - details: Vue CLI 完全是可配置的,无需 eject。这样你的项目就可以长期保持更新了。 -- title: CLI 之上的图形化界面 - details: 通过配套的图形化界面创建、开发和管理你的项目。 -- title: 即刻创建原型 - details: 用单个 Vue 文件即刻实践新的灵感。 -- title: 面向未来 - details: 为现代浏览器轻松产出原生的 ES2015 代码,或将你的 Vue 组件构建为原生的 Web Components 组件。 -footer: MIT Licensed | Copyright © 2018-present Evan You ---- - -## 起步 - -安装: - -``` bash -npm install -g @vue/cli -# OR -yarn global add @vue/cli -``` - -创建一个项目: - -``` bash -vue create my-project -# OR -vue ui -``` diff --git a/docs/zh/config/README.md b/docs/zh/config/index.md similarity index 90% rename from docs/zh/config/README.md rename to docs/zh/config/index.md index 94d131fa26..08529ad8d5 100644 --- a/docs/zh/config/README.md +++ b/docs/zh/config/index.md @@ -22,13 +22,29 @@ sidebar: auto ``` js // vue.config.js + +/** + * @type {import('@vue/cli-service').ProjectOptions} + */ module.exports = { // 选项... } ``` + +或者,你也可以使用 `@vue/cli-service` 提供的 `defineConfig` 帮手函数,以获得更好的类型提示: + +```js +// vue.config.js +const { defineConfig } = require('@vue/cli-service') + +module.exports = defineConfig({ + // 选项 +}) +``` + ### baseUrl -从 Vue CLI 3.3 起已弃用,请使用[`publicPath`](#publicPath)。 +从 Vue CLI 3.3 起已弃用,请使用[`publicPath`](#publicpath)。 ### publicPath @@ -65,7 +81,7 @@ module.exports = { - Type: `string` - Default: `'dist'` - 当运行 `vue-cli-service build` 时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入 `--no-clean` 可关闭该行为)。 + 当运行 `vue-cli-service build` 时生成的生产环境构建文件的目录。注意目标目录的内容在构建之前会被清除 (构建时传入 `--no-clean` 可关闭该行为)。 ::: tip 提示 请始终使用 `outputDir` 而不要修改 webpack 的 `output.path`。 @@ -138,14 +154,16 @@ module.exports = { ### lintOnSave -- Type: `boolean` | `'error'` -- Default: `true` +- Type: `boolean` | `'warning'` | `'default'` | `'error'` +- Default: `'default'` 是否在开发环境下通过 [eslint-loader](https://github.com/webpack-contrib/eslint-loader) 在每次保存时 lint 代码。这个值会在 [`@vue/cli-plugin-eslint`](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint) 被安装之后生效。 - 设置为 `true` 时,`eslint-loader` 会将 lint 错误输出为编译警告。默认情况下,警告仅仅会被输出到命令行,且不会使得编译失败。 + 设置为 `true` 或 `'warning'` 时,`eslint-loader` 会将 lint 错误输出为编译警告。默认情况下,警告仅仅会被输出到命令行,且不会使得编译失败。 + + 如果你希望让 lint 错误在开发时直接显示在浏览器中,你可以使用 `lintOnSave: 'default'`。这会强制 `eslint-loader` 将 lint 错误输出为编译错误,同时也意味着 lint 错误将会导致编译失败。 - 如果你希望让 lint 错误在开发时直接显示在浏览器中,你可以使用 `lintOnSave: 'error'`。这会强制 `eslint-loader` 将 lint 错误输出为编译错误,同时也意味着 lint 错误将会导致编译失败。 + 设置为 `error` 将会使得 `eslint-loader` 把 lint 警告也输出为编译错误,这意味着 lint 警告将会导致编译失败。 或者,你也可以通过设置让浏览器 overlay 同时显示警告和错误: @@ -181,10 +199,13 @@ module.exports = { ### transpileDependencies -- Type: `Array` -- Default: `[]` +- Type: `boolean | Array` +- Default: `false` + + 默认情况下 `babel-loader` 会忽略所有 `node_modules` 中的文件。你可以启用本选项,以避免构建后的代码中出现未转译的第三方依赖。 + + 不过,对所有的依赖都进行转译可能会降低构建速度。如果对构建性能有所顾虑,你可以只转译部分特定的依赖:给本选项传一个数组,列出需要转译的第三方包包名或正则表达式即可。 - 默认情况下 `babel-loader` 会忽略所有 `node_modules` 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。 ### productionSourceMap @@ -221,7 +242,7 @@ module.exports = { 如果这个值是一个对象,则会通过 [webpack-merge](https://github.com/survivejs/webpack-merge) 合并到最终的配置中。 - 如果这个值是一个函数,则会接收被解析的配置作为参数。该函数及可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。 + 如果这个值是一个函数,则会接收被解析的配置作为参数。该函数既可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。 更多细节可查阅:[配合 webpack > 简单的配置方式](../guide/webpack.md#简单的配置方式) @@ -317,7 +338,7 @@ module.exports = { - 有些值像 `host`、`port` 和 `https` 可能会被命令行参数覆写。 - - 有些值像 `publicPath` 和 `historyApiFallback` 不应该被修改,因为它们需要和开发服务器的 [publicPath](#baseurl) 同步以保障正常的工作。 + - 有些值像 `publicPath` 和 `historyApiFallback` 不应该被修改,因为它们需要和开发服务器的 [publicPath](#publicpath) 同步以保障正常的工作。 ### devServer.proxy diff --git a/docs/zh/dev-guide/plugin-dev.md b/docs/zh/dev-guide/plugin-dev.md index 6a422c39d9..8c71d9b45b 100644 --- a/docs/zh/dev-guide/plugin-dev.md +++ b/docs/zh/dev-guide/plugin-dev.md @@ -4,75 +4,416 @@ sidebarDepth: 3 # 插件开发指南 -## 核心概念 +## 开始 -系统里有两个主要的部分: +一个 CLI 插件是一个 npm 包,它能够为 Vue CLI 创建的项目添加额外的功能,这些功能包括: -- `@vue/cli`:全局安装的,暴露 `vue create ` 命令; -- `@vue/cli-service`:局部安装,暴露 `vue-cli-service` 命令。 +- 修改项目的 webpack 配置 - 例如,如果你的插件希望去针对某种类型的文件工作,你可以为这个特定的文件扩展名添加新的 webpack 解析规则。比如说,`@vue/cli-plugin-typescript` 就添加这样的规则来解析 `.ts` 和 `.tsx` 扩展的文件; +- 添加新的 vue-cli-service 命令 - 例如,`@vue/cli-plugin-unit-jest` 添加了 `test:unit` 命令,允许开发者运行单元测试; +- 扩展 `package.json` - 当你的插件添加了一些依赖到项目中,你需要将他们添加到 package 的 dependencies 部分时,这是一个有用的选项; +- 在项目中创建新文件、或者修改老文件。有时创建一个示例组件或者通过给入口文件(main.js)添加导入(imports)是一个好的主意; +- 提示用户选择一个特定的选项 - 例如,你可以询问用户是否创建我们前面提到的示例组件。 -两者皆应用了基于插件的架构。 +:::tip +不要过度使用 vue-cli 插件!如果你仅希望包含特定的插件,例如,[Lodash](https://lodash.com/) - 相比创建一个特定的插件,通过 npm 手动安装更加简单。 +::: + +CLI 插件应该总是包含一个 [service 插件](#service-plugin) 做为主的导出,并且他能够选择性的包含 [generator](#generator), [prompt 文件](#prompts) 和 [Vue UI 集成](#ui-integration)。 + +作为一个 npm 包,CLI 插件必须有一个 `package.json` 文件。通常建议在 `README.md` 中包含插件的描述,来帮助其他人在 npm 上发现你的插件。 -### Creator +所以,通常的 CLI 插件目录结构看起来像下面这样: -[Creator][creator-class] 是调用 `vue create ` 时创建的类。负责偏好对话、调用 generator 和安装依赖。 +```bash +. +├── README.md +├── generator.js # generator(可选) +├── index.js # service 插件 +├── package.json +├── prompts.js # prompt 文件(可选) +└── ui.js # Vue UI 集成(可选) +``` + +## 命名和可发现性 + +为了让一个 CLI 插件在 Vue CLI 项目中被正常使用,它必须遵循 `vue-cli-plugin-` 或者 `@scope/vue-cli-plugin-` 这样的命名惯例。这样你的插件才能够: + +- 被 `@vue/cli-service` 发现; +- 被其他开发者通过搜索发现; +- 通过 `vue add ` 或者 `vue invoke ` 安装。 -### Service +:::warning Warning +确保插件的名字是正确的,否则他将不能通过 `vue add` 安装并且不能在 UI 插件中搜索得到! +::: -[Service][service-class] 是调用 `vue-cli-service [...args]` 时创建的类。负责管理内部的 webpack 配置、暴露服务和构建项目的命令等。 +为了能够被用户在搜索时更好的发现,可以将插件的关键描述放到 `package.json` 文件的 `description` 字段中。 -### CLI 插件 +例如: -CLI 插件是一个可以为 `@vue/cli` 项目添加额外特性的 npm 包。它应该始终包含一个 [Service 插件](#service-插件)作为其主要导出,且可选的包含一个 [Generator](#generator) 和一个 [Prompt 文件](#第三方插件的对话)。 +```json +{ + "name": "vue-cli-plugin-apollo", + "version": "0.7.7", + "description": "vue-cli plugin to add Apollo and GraphQL" +} +``` -一个典型的 CLI 插件的目录结构看起来是这样的: +你应该在 `homepage` 或者 `repository` 字段添加创建插件的官网地址或者仓库的地址,这样你的插件详情里就会出现一个 `查看详情` 按钮: +```json +{ + "repository": { + "type": "git", + "url": "git+https://github.com/Akryum/vue-cli-plugin-apollo.git" + }, + "homepage": "https://github.com/Akryum/vue-cli-plugin-apollo#readme" +} ``` -. -├── README.md -├── generator.js # generator (可选) -├── prompts.js # prompt 文件 (可选) -├── index.js # service 插件 -└── package.json + +![Plugin search item](/plugin-search-item.png) + +## Generator + +插件的 Generator 部分通常在你想要为项目扩展包依赖,创建新的文件或者编辑已经存在的文件时需要。 + +在 CLI 插件内部,generator 应该放在 `generator.js` 或者 `generator/index.js` 文件中。它将在以下两个场景被调用: + +- 项目初始创建期间,CLI 插件被作为项目创建 preset 的一部分被安装时。 + +- 当插件在项目创建完成和通过 `vue add` 或者 `vue invoke` 单独调用被安装时。 + +一个 generator 应该导出一个接收三个参数的函数: + +1. 一个 [GeneratorAPI](/dev-guide/generator-api.md) 实例; + +2. 插件的 generator 选项。这些选项在项目创建,或者从 `~/.vuerc` 载入预设时被解析。例如:如果保存的 `~/.vuerc` 像这样: + +```json +{ + "presets" : { + "foo": { + "plugins": { + "@vue/cli-plugin-foo": { "option": "bar" } + } + } + } +} +``` + +如果用户使用 preset `foo` 创建了一个项目,那么 `@vue/cli-plugin-foo` 的 generator 就会收到 `{ option: 'bar' }` 作为第二个参数。 + +对于第三方插件,这个选项将在用户执行 `vue invoke` 时,从提示或者命令行参数中被解析(详见 [对话](#对话))。 + +3. 整个 preset (presets.foo) 将会作为第三个参数传入。 + +### 创建新的模板 + +当你调用 `api.render('./template')` 时,该 generator 将会使用 [EJS](https://github.com/mde/ejs) 渲染 `./template` 中的文件 (相对于 generator 中的文件路径进行解析) + +想象我们正在创建 [vue-cli-auto-routing](https://github.com/ktsn/vue-cli-plugin-auto-routing) 插件,我们希望当插件在项目中被引用时做以下的改变: + +- 创建一个 `layouts` 文件夹包含默认布局文件; +- 创建一个 `pages` 文件夹包含 `about` 和 `home` 页面; +- 在 `src` 文件夹中添加 `router.js` 文件 + +为了渲染这个结构,你需要在 `generator/template` 文件夹内创建它: + +![Generator structure](/generator-template.png) + +模板创建完之后,你应该在 `generator/index.js` 文件中添加 `api.render` 调用: + +```js +module.exports = api => { + api.render('./template') +} +``` + +### 编辑已经存在的模板 + +此外,你可以使用 YAML 前置元信息继承并替换已有的模板文件的一部分(即使来自另一个包): + +```ejs +--- +extend: '@vue/cli-service/generator/template/src/App.vue' +replace: !!js/regexp / +``` + +也可以替换多处,只不过你需要将替换的字符串包裹在 `<%# REPLACE %>` 和 `<%# END_REPLACE %>` 块中: + +```ejs +--- +extend: '@vue/cli-service/generator/template/src/App.vue' +replace: + - !!js/regexp /Welcome to Your Vue\.js App/ + - !!js/regexp / +<%# END_REPLACE %> +``` + +### 文件名的边界情况 + +如果你想要渲染一个以点开头的模板文件 (例如 `.env`),则需要遵循一个特殊的命名约定,因为以点开头的文件会在插件发布到 npm 的时候被忽略: + +```bash +# 以点开头的模板需要使用下划线取代那个点: + +/generator/template/_env + +# 当调用 api.render('./template') 时,它在项目文件夹中将被渲染为: + +/generator/template/.env +``` + +同时这也意味着当你想渲染以下划线开头的文件时,同样需要遵循一个特殊的命名约定: + +```bash +# 这种模板需要使用两个下划线来取代单个下划线: + +/generator/template/__variables.scss + +# 当调用 api.render('./template') 时,它在项目文件夹中将被渲染为: + +/generator/template/_variable.scss +``` + +### 扩展包 + +如果你需要向项目中添加额外的依赖,创建一个 npm 脚本或者修改 `package.json` 的其他任何一处,你可以使用 API `extendPackage` 方法。 + +```js +// generator/index.js + +module.exports = api => { + api.extendPackage({ + dependencies: { + 'vue-router-layout': '^0.1.2' + } + }) +} +``` + +在上面这个例子中,我们添加了一个依赖:`vue-router-layout`。在插件调用时,这个 npm 模块将被安装,这个依赖将被添加到用户项目的 `package.json` 文件。 + +同样使用这个 API 我们可以添加新的 npm 任务到项目中。为了实现这个,我们需要定义一个任务名和一个命令,这样他才能够在用户 `package.json` 文件的 `scripts` 部分运行: + +```js +// generator/index.js + +module.exports = api => { + api.extendPackage({ + scripts: { + greet: 'vue-cli-service greet' + } + }) +} +``` + +在上面这个例子中,我们添加了一个新的 `greet` 任务来执行一个创建在 [Service 部分](#add-a-new-cli-service-command) 的自定义 vue-cli 服务命令。 + +### 修改主文件 + +通过 generator 方法你能够修改项目中的文件。最有用的场景是针对 `main.js` 或 `main.ts` 文件的一些修改:新的导入,新的 `Vue.use()` 调用等。 + +让我们来思考一个场景,当我们通过 [模板](#creating-new-templates) 创建了一个 `router.js` 文件,现在我们希望导入这个路由到主文件中。我们将用到两个 generator API 方法: `entryFile` 将返回项目的主文件(`main.js` 或 `main.ts`),`injectImports` 用于添加新的导入到主文件中: + +```js +// generator/index.js + +api.injectImports(api.entryFile, `import router from './router'`) ``` -### Service 插件 +现在,当我们路由被导入时,我们可以在主文件中将这个路由注入到 Vue 实例。我们可以使用 `afterInvoke` 钩子,这个钩子将在文件被写入硬盘之后被调用。 -Service 插件会在一个 Service 实例被创建时自动加载——比如每次 `vue-cli-service` 命令在项目中被调用时。 +首先,我们需要通过 Node 的 `fs` 模块(提供了文件交互 API)读取文件内容,将内容拆分 -注意我们这里讨论的“service 插件”的概念要比发布为一个 npm 包的“CLI 插件”的要更窄。前者涉及一个会被 `@vue/cli-service` 在初始化时加载的模块,也经常是后者的一部分。 +```js +// generator/index.js -此外,`@vue/cli-service` 的[内建命令][commands]和[配置模块][config]也是全部以 service 插件实现的。 +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const fs = require('fs') + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) + }) +} +``` + +然后我们需要找到包含 `render` 单词的字符串(它通常是 Vue 实例的一部分),`router` 就是下一个字符串: + +```js{9-10} +// generator/index.js + +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const fs = require('fs') + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) + + const renderIndex = lines.findIndex(line => line.match(/render/)) + lines[renderIndex] += `\n router,` + }) +} +``` + +最后,你需要将内容写入主文件: + +```js{12-13} +// generator/index.js + +module.exports.hooks = (api) => { + api.afterInvoke(() => { + const { EOL } = require('os') + const fs = require('fs') + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g) + + const renderIndex = lines.findIndex(line => line.match(/render/)) + lines[renderIndex] += `${EOL} router,` + + fs.writeFileSync(api.entryFile, lines.join(EOL), { encoding: 'utf-8' }) + }) +} +``` + +## Service 插件 + +Service 插件可以修改 webpack 配置,创建新的 vue-cli service 命令或者修改已经存在的命令(如 `serve` 和 `build`)。 + +Service 插件在 Service 实例被创建后自动加载 - 例如,每次 `vue-cli-service` 命令在项目中被调用的时候。它位于 CLI 插件根目录的 `index.js` 文件。 一个 service 插件应该导出一个函数,这个函数接受两个参数: -- 一个 [PluginAPI][plugin-api] 实例 +- 一个 [PluginAPI](/dev-guide/plugin-api.md) 实例 - 一个包含 `vue.config.js` 内指定的项目本地选项的对象,或者在 `package.json` 内的 `vue` 字段。 -这个 API 允许 service 插件针对不同的环境扩展/修改内部的 webpack 配置,并向 `vue-cli-service` 注入额外的命令。例如: +一个 service 插件至少应包含如下代码: -``` js -module.exports = (api, projectOptions) => { +```js +module.exports = () => {} +``` + +### 修改 webpack 配置 + +这个 API 允许 service 插件针对不同的环境扩展/修改内部的 webpack 配置。例如,这里我们在 webpack-chain 中添加 `vue-auto-routing` 这个 webpack 插件,并指定参数: + +```js +const VueAutoRoutingPlugin = require('vue-auto-routing/lib/webpack-plugin') + +module.exports = (api, options) => { api.chainWebpack(webpackConfig => { - // 通过 webpack-chain 修改 webpack 配置 + webpackConfig + .plugin('vue-auto-routing') + .use(VueAutoRoutingPlugin, [ + { + pages: 'src/pages', + nested: true + } + ]) }) +} +``` - api.configureWebpack(webpackConfig => { - // 修改 webpack 配置 - // 或返回通过 webpack-merge 合并的配置对象 - }) +你也可以使用 `configureWebpack` 方法修改 webpack 配置或者返回一个对象,返回的对象将通过 webpack-merge 被合并到配置中。 + +### 添加一个新的 cli-service 命令 + +通过 service 插件你可以注册一个新的 cli-service 命令,除了标准的命令(即 `serve` 和 `build`)。你可以使用 `registerCommand` API 方法实现。 + +下面的例子创建了一个简单的新命令,可以向开发控制台输出一条问候语: + +```js +api.registerCommand( + 'greet', + { + description: 'Write a greeting to the console', + usage: 'vue-cli-service greet' + }, + () => { + console.log(`👋 Hello`) + } +) +``` + +在这个例子中,我们提供了命令的名字(`'greet'`)、一个有 `description` 和 `usage` 选项的对象,和一个在执行 `vue-cli-service greet` 命令时会调用的函数。 + +:::tip +你可以 [通过 Generator](#extending-package) 添加一个新的命令到项目 `package.json` 文件的 npm 脚本列表中。 +::: + +如果你在已经安装了插件的项目中运行新命令,你将看到下面的输出: + +```bash +$ vue-cli-service greet +👋 Hello! +``` - api.registerCommand('test', args => { - // 注册 `vue-cli-service test` +你也可以给新命令定义一系列可能的选项。接下来我们添加一个 `--name` 选项,并修改实现函数,当提供了 name 参数时把它也打印出来。 + +```js +api.registerCommand( + 'greet', + { + description: 'Writes a greeting to the console', + usage: 'vue-cli-service greet [options]', + options: { '--name': 'specifies a name for greeting' } + }, + args => { + if (args.name) { + console.log(`👋 Hello, ${args.name}!`); + } else { + console.log(`👋 Hello!`); + } + } +) +``` + +现在,如果 `greet` 命令携带了特定的 `--name` 选项,这个 name 被添加到控制台输出: + +```bash +$ vue-cli-service greet --name 'John Doe' +👋 Hello, John Doe! +``` + +### 修改已经存在的 cli-service 命令 + +如果你想修改一个已经存在的 cli-service 命令,你可以使用 `api.service.commands` 获取到命令对象并且做些改变。我们将在应用程序运行的端口打印一条信息到控制台: + +```js +const { serve } = api.service.commands + +const serveFn = serve.fn + +serve.fn = (...args) => { + return serveFn(...args).then(res => { + if(res && res.url) { + console.log(`Project is running now at ${res.url}`) + } }) } ``` -#### 为命令指定模式 +在上面的这个例子中,我们从已经存在的命令列表中获取到命令对象 `serve`;然后我们修改了他的 `fn` 部分(`fn` 是创建这个新命令时传入的第三个参数;它定义了在执行这个命令时要执行的函数)。修改完后,这个控制台消息将在 `serve` 命令成功运行后打印。 - -> 注意:插件设置模式的方式从 beta.10 开始已经改变了。 +### 为命令指定模式 如果一个已注册的插件命令需要运行在特定的默认模式下,则该插件需要通过 `module.exports.defaultModes` 以 `{ [commandName]: mode }` 的形式来暴露: @@ -90,249 +431,448 @@ module.exports.defaultModes = { 这是因为我们需要在加载环境变量之前知道该命令的预期模式,所以需要提前加载用户选项/应用插件。 -#### 在插件中解析 webpack 配置 +## 对话 -一个插件可以通过调用 `api.resolveWebpackConfig()` 取回解析好的 webpack 配置。每次调用都会新生成一个 webpack 配置用来在需要时进一步修改。 +对话是在创建一个新的项目或者在已有项目中添加新的插件时处理用户选项时需要的。所有的对话逻辑都存储在 `prompts.js` 文件中。对话内部是通过 [inquirer](https://github.com/SBoudrias/Inquirer.js) 实现。 -``` js -module.exports = api => { - api.registerCommand('my-build', args => { - const configA = api.resolveWebpackConfig() - const configB = api.resolveWebpackConfig() +当用户通过调用 `vue invoke` 初始化插件时,如果插件根目录包含 `prompts.js`,他将在调用时被使用。这个文件应该导出一个[问题](https://github.com/SBoudrias/Inquirer.js#question)数组 -- 将被 Inquirer.js 处理。 - // 针对不同的目的修改 `configA` 和 `configB`... - }) -} +你应该直接导出一个问题数组,或者导出一个返回这些内容的函数。 -// 请确保为正确的环境变量指定默认模式 -module.exports.defaultModes = { - 'my-build': 'production' -} +例如,直接是问题数组: +```js +// prompts.js + +module.exports = [ + { + type: 'input', + name: 'locale', + message: 'The locale of project localization.', + validate: input => !!input, + default: 'en' + } + // ... +] ``` -或者,一个插件也可以通过调用 `api.resolveChainableWebpackConfig()` 获得一个新生成的[链式配置](https://github.com/mozilla-neutrino/webpack-chain): +例如,一个返回问题数组的函数: +```js +// prompts.js -``` js -api.registerCommand('my-build', args => { - const configA = api.resolveChainableWebpackConfig() - const configB = api.resolveChainableWebpackConfig() +// 将 `package.json` 作为参数传入函数 +module.exports = pkg => { + const prompts = [ + { + type: 'input', + name: 'locale', + message: 'The locale of project localization.', + validate: input => !!input, + default: 'en' + } + ] + + // 添加动态对话 + if ('@vue/cli-plugin-eslint' in (pkg.devDependencies || {})) { + prompts.push({ + type: 'confirm', + name: 'useESLintPluginVueI18n', + message: 'Use ESLint plugin for Vue I18n ?' + }) + } + + return prompts +} +``` - // 针对不同的目的链式修改 `configA` 和 `configB`... +解析到的答案对象将作为选项传入到插件的 generator。 - const finalConfigA = configA.toConfig() - const finalConfigB = configB.toConfig() -}) +或者,用户可以通过在命令行传入选项跳过对话直接初始化插件,例如: + +```bash +vue invoke my-plugin --mode awesome ``` -#### 第三方插件的自定义选项 +对话可以有[不同的类型](https://github.com/SBoudrias/Inquirer.js#prompt-types),但是在 CLI 大多数使用的是 `checkbox` 和 `confirm`。让我们添加一个 `confirm` 对话,然后在插件的 generator 使用它,来创建一个有条件的[模板渲染](#creating-new-templates)。 -`vue.config.js` 的导出将会[通过一个 schema 的验证](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/options.js#L3)以避免笔误和错误的配置值。然而,一个第三方插件仍然允许用户通过 `pluginOptions` 字段配置其行为。例如,对于下面的 `vue.config.js`: +```js +// prompts.js -``` js -module.exports = { - pluginOptions: { - foo: { /* ... */ } +module.exports = [ + { + name: `addExampleRoutes`, + type: 'confirm', + message: 'Add example routes?', + default: false } +] +``` + +插件被调用时,用户将被问到示例路由的问题,默认的答案是 `No`。 + +![Prompts example](/prompts-example.png) + +如果你想在 generator 中使用用户的选择结果,你可以通过对话名字获得。我们可以修改一下 `generator/index.js`: + +```js +if (options.addExampleRoutes) { + api.render('./template', { + ...options + }) } ``` -该第三方插件可以读取 `projectOptions.pluginOptions.foo` 来做条件式的决定配置。 +现在如果用户同意创建示例路由,那么模板将被渲染。 -### Generator +## 安装本地插件 -一个发布为 npm 包的 CLI 插件可以包含一个 `generator.js` 或 `generator/index.js` 文件。插件内的 generator 将会在两种场景下被调用: +当你开发自己的插件时,你需要测试它、查看它在使用 Vue CLI 创建的项目中如何工作。你可以使用已经存在的项目或者创建一个新的项目用来测试: -- 在一个项目的初始化创建过程中,如果 CLI 插件作为项目创建 preset 的一部分被安装。 +```bash +vue create test-app +``` -- 插件在项目创建好之后通过 `vue invoke` 独立调用时被安装。 +安装插件,在项目根目录运行下面的命令: -这里的 [GeneratorAPI][generator-api] 允许一个 generator 向 `package.json` 注入额外的依赖或字段,并向项目中添加文件。 +```bash +npm install --save-dev file:/full/path/to/your/plugin +vue invoke +``` -一个 generator 应该导出一个函数,这个函数接收三个参数: +每次插件修改后,你需要重复这个步骤。 -1. 一个 `GeneratorAPI` 实例: +另一个方式是利用 Vue UI 的能力来添加插件。你可以运行它: -2. 这个插件的 generator 选项。这些选项会在项目创建对话过程中被解析,或从一个保存在 `~/.vuerc` 中的 preset 中加载。例如,如果保存好的 `~/.vuerc` 像如下的这样: +```bash +vue ui +``` - ``` json - { - "presets" : { - "foo": { - "plugins": { - "@vue/cli-plugin-foo": { "option": "bar" } - } - } - } - } - ``` +将打开浏览器的窗口地址 `localhost:8000`。到 `Vue 项目管理` 菜单栏: - 如果用户使用 preset `foo` 创建了一个项目,那么 `@vue/cli-plugin-foo` 的 generator 就会收到 `{ option: 'bar' }` 作为第二个参数。 +![Vue Project Manager](/ui-project-manager.png) - 对于一个第三方插件来说,该选项将会解析自对话或用户执行 `vue invoke` 时的命令行参数中 (详见[第三方插件的对话](#第三方插件的对话))。 +然后找到你的测试项目的名字: -3. 整个 preset (`presets.foo`) 将会作为第三个参数传入。 +![UI Plugins List](/ui-select-plugin.png) -**示例:** +点击应用名字,到插件菜单(有个拼图图标)然后点击右上角的 `添加新的插件` 按钮。在新页面中你将看到一系列能够通过 npm 获得的 Vue CLI 插件。在页面底部有一个 `浏览本地插件` 的按钮: -``` js -module.exports = (api, options, rootOptions) => { - // 修改 `package.json` 里的字段 - api.extendPackage({ - scripts: { - test: 'vue-cli-service test' - } - }) +![Browse local plugins](/ui-browse-local-plugin.png) - // 复制并用 ejs 渲染 `./template` 内所有的文件 - api.render('./template') +点击它之后,你能够轻松的搜索到你的插件并添加到项目中。在这之后你可以在插件列表中看到这个插件,并且简单的点击下 `刷新` 图标即可同步对插件代码所做的修改: - if (options.foo) { - // 有条件地生成文件 - } -} -``` +![Refresh plugin](/ui-plugin-refresh.png) -#### Generator 的模板处理 +## UI 集成 -当你调用 `api.render('./template')` 时,该 generator 将会使用 [EJS](https://github.com/mde/ejs) 渲染 `./template` 中的文件 (相对于 generator 中的文件路径进行解析) +Vue CLI 有一个非常强大的 UI 工具 -- 允许用户通过图形接口来架构和管理项目。Vue CLI 插件能够集成到接口中。UI 为 CLI 插件提供了额外的功能: -此外,你可以使用 YAML 前置元信息继承并替换已有的模板文件的一部分: +- 你可以执行 npm 任务,直接在 UI 中执行插件中定义的命令; +- 你可以展示插件的自定义配置。例如: [vue-cli-plugin-apollo](https://github.com/Akryum/vue-cli-plugin-apollo) 针对 Apollo 服务器提供了如下的配置: -``` ejs ---- -extend: '@vue/cli-service/generator/template/src/App.vue' -replace: !!js/regexp / ``` -你也可以完成多处替换,当然你需要将要替换的字符串用 `<%# REPLACE %>` 和 `<%# END_REPLACE %>` 块包裹起来: +### 为任务增加 UI 界面 -``` ejs ---- -extend: '@vue/cli-service/generator/template/src/App.vue' -replace: - - !!js/regexp /欢迎来到你的 Vue\.js 应用/ - - !!js/regexp / -<%# END_REPLACE %> ``` -#### 文件名的极端情况 +现在如果你在 Vue UI 中浏览你的项目,你会发现添加到 `Tasks` 部分的任务。你可以看见任务的名字、描述信息、指向你提供的 URL 的链接图标和一个展示任务输出的输出窗口: -如果你想要渲染一个以点开头的模板文件 (例如 `.env`),则需要遵循一个特殊的命名约定,因为以点开头的文件会在插件发布到 npm 的时候被忽略: +![UI Greet task](/ui-greet-task.png) + +### 展示配置页面 + +有时你的项目针对不同的功能或者库,有自定义的配置文件。通过 Vue CLI 插件,你可以在 Vue UI 中展示配置,修改它和保存它(保存将修改你项目中相应的配置)。默认情况下,Vue CLI 项目有个主配置页面对应 `vue.config.js` 的配置。如果你将 ESLint 包含到项目中,你可以看到一个 ESLint 的配置页面: + +![UI Configuration Screen](/ui-configuration-default.png) + +让我们为你的插件建一个自定义的配置。第一步,在你的插件添加到已经存在的项目中之后,应该有个配置文件。这意味着你需要在[模板步骤](#creating-new-templates)将这个文件添加到 `template` 文件夹中。 + +默认情况下,一个可配置的 UI 能够读取和写入以下文件类型:`json`,`yaml`,`js`,`package`。让我们命名文件为 `myConfig.js` 将它放入 `template` 的根文件夹: ``` -# 以点开头的模板需要使用下划线取代那个点: +. +└── generator + ├── index.js + └── template + ├── myConfig.js + └── src + ├── layouts + ├── pages + └── router.js +``` -/generator/template/_env +现在你需要添加一些真实的配置到这个文件中: -# 调用 api.render('./template') 会在项目目录中渲染成为: +```js +// myConfig.js -.env +module.exports = { + color: 'black' +} ``` -同时这也意味着当你想渲染以下划线开头的文件时,同样需要遵循一个特殊的命名约定: +当你的插件被应用后,`myConfig.js` 文件将被渲染到项目根目录。现在让我们在 `ui.js` 文件中通过 `api.describeConfig` 方法添加一个新的配置页面。 + +首先你需要传入一些信息: +```js +// ui.js + +api.describeConfig({ + // 配置的唯一id + id: 'org.ktsn.vue-auto-routing.config', + // 展示的名字 + name: 'Greeting configuration', + // 展示在名字下面 + description: 'This config defines the color of the greeting printed', + // “查看详情” 的链接 + link: 'https://github.com/ktsn/vue-cli-plugin-auto-routing#readme' +}) ``` -# 这种模板需要使用两个下划线来取代单个下划线: -/generator/template/__variables.scss +:::danger Warning +确保正确地为 id 设置命名空间,它必须在所有的插件中唯一。建议使用 [reverse domain name notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) 命名方法 +::: -# 调用 api.render('./template') 会在项目目录中渲染成为: +### 配置 logo -_variables.scss +你也可以为你的配置选择一个图标。他既可以是 [Material icon](https://material.io/tools/icons/?style=baseline) 代码,也可以是自定义图片(看这里 [Public static files](ui-api.md#public-static-files))。 + +```js +// ui.js + +api.describeConfig({ + /* ... */ + // Config icon + icon: 'color_lens' +}) ``` +如果你不定义图标,将展示插件logo (看这里 [Logo](#logo))。 -### Prompts +#### 配置文件 -#### 内建插件的对话 +现在你需要将配置文件提供给 UI:这样你可以读取它的内容或者修改它。你需要为你的配置文件选择一个名字,选择格式和提供文件路径: -只有内建插件可以定制创建新项目时的初始化对话,且这些对话模块放置在 [`@vue/cli` 包的内部][prompt-modules]。 +```js +api.describeConfig({ + // other config properties + files: { + myConfig: { + js: ['myConfig.js'] + } + } +}) +``` -一个对话模块应该导出一个函数,这个函数接收一个 [PromptModuleAPI][prompt-api] 实例。这些对话的底层使用 [inquirer](https://github.com/SBoudrias/Inquirer.js) 进行展示: +这里可以提供多个文件。如果我们有 `myConfig.json`,我们使用 `json: ['myConfig.json']` 属性提供它。顺序很重要:如果配置文件不存在,列表中的第一个文件名将被用于创建它。 -``` js -module.exports = api => { - // 一个特性对象应该是一个有效的 inquirer 选择对象 - api.injectFeature({ - name: 'Some great feature', - value: 'my-feature' - }) +#### 展示配置的对话 - // injectPrompt 期望接收一个有效的 inquirer 对话对象 - api.injectPrompt({ - name: 'someFlag', - // 确认对话只在用户已经选取了特性的时候展示 - when: answers => answers.features.include('my-feature'), - message: 'Do you want to turn on flag foo?', - type: 'confirm' - }) +我们希望在配置页面中展示一个颜色属性的输入框。为了完成它,我们需要 `onRead` 钩子,它将返回一个被展示的对话列表: - // 当所有的对话都完成之后,将你的插件注入到 - // 即将传递给 Generator 的 options 中 - api.onPromptComplete((answers, options) => { - if (answers.features.includes('my-feature')) { - options.plugins['vue-cli-plugin-my-feature'] = { - someFlag: answers.someFlag +```js +api.describeConfig({ + onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Define the color for greeting message', + value: 'white' } - } + ] }) -} +}) ``` -#### 第三方插件的对话 +上面这个例子中,我们定义值为 'white' 的输入对话。加了以上所有设置后,我们的配置页面看起来会是这样的: -第三方插件通常会在一个项目创建完毕后被手动安装,且用户将会通过调用 `vue invoke` 来初始化这个插件。如果这个插件在其根目录包含一个 `prompts.js`,那么它将会用在该插件被初始化调用的时候。这个文件应该导出一个用于 Inquirer.js 的[问题](https://github.com/SBoudrias/Inquirer.js#question)的数组。这些被解析的答案对象会作为选项被传递给插件的 generator。 +![UI Config Start](/ui-config-start.png) -或者,用户可以通过在命令行传递选项来跳过对话直接初始化插件,比如: +现在让我们使用来自配置文件的属性,替换硬编码的 `white` 值。在 `onRead` 钩子中 `data` 对象包含每一个配置文件内容的 JSON 结果。在我们的情况下,`myConfig.js` 的内容是 -``` bash -vue invoke my-plugin --mode awesome +```js +// myConfig.js + +module.exports = { + color: 'black' +} ``` -## 发布插件 +所以,`data` 对象将是 -为了让一个 CLI 插件能够被其它开发者使用,你必须遵循 `vue-cli-plugin-` 的命名约定将其发布到 npm 上。插件遵循命名约定之后就可以: +```js +{ + // File + myConfig: { + // File data + color: 'black' + } +} +``` -- 被 `@vue/cli-service` 发现; -- 被其它开发者搜索到; -- 通过 `vue add ` 或 `vue invoke ` 安装下来。 +容易看到,我们需要 `data.myConfig.color` 属性。让我们修改 `onRead` 钩子: -## 开发核心插件的注意事项 +```js +// ui.js + +onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Define the color for greeting message', + value: data.myConfig && data.myConfig.color + } + ] +}), +``` -::: tip 注意 -这个章节只用于 `vuejs/vue-cli` 仓库内部的内建插件工作。 +::: tip +注意,当页面加载时,如果配置文件不存在 `myConfig` 可能是 undefined。 ::: -一个带有为本仓库注入额外依赖的 generator 的插件 (比如 `chai` 会通过 `@vue/cli-plugin-unit-mocha/generator/index.js` 被注入) 应该将这些依赖列入其自身的 `devDependencies` 字段。这会确保: +你可以看见,在配置页面中 `white` 被 `black` 替换了。 + +如果配置文件不存在,我们可以提供一个默认值: + +```js +// ui.js + +onRead: ({ data }) => ({ + prompts: [ + { + name: `color`, + type: 'input', + message: 'Define the color for greeting message', + value: data.myConfig && data.myConfig.color, + default: 'black', + } + ] +}), +``` + +#### 保存配置变化 + +我们刚刚读取了 `myConfig.js` 的内容并且在配置页面使用它。现在让我们尝试将颜色输入框的内容保存到文件中。我们可以使用 `onWrite` 钩子: + +```js +// ui.js + +api.describeConfig({ + /* ... */ + onWrite: ({ prompts, api }) => { + // ... + } +}) +``` -1. 这个包始终存在于该仓库的根 `node_modules` 中,因此我们不必在每次测试的时候重新安装它们。 +`onWrite` 钩子能够得到许多[参数](ui-api.html#save-config-changes) 但我们仅仅需要其中的两个:`prompts` 和 `api`。第一个是当前对话运行时对象 - 我们将得到对话 id 并且通过 id 拿到答案。为了获取答案我们需要使用来自 `api` 的 `async getAnswer()` 方法: + +```js +// ui.js + +async onWrite({ api, prompts }) { + const result = {} + for (const prompt of prompts) { + result[`${prompt.id}`] = await api.getAnswer(prompt.id) + } + api.setData('myConfig', result) +} +``` + +现在如果你通过配置页面修改颜色输入框的内容,有 `black` 变为 `red`,然后按下 `保存修改` 按钮,你会发现你的项目中的 `myConfig.js` 文件也发生了变化: + +```js +// myConfig.js + +module.exports = { + color: 'red' +} +``` -2. `yarn.lock` 会保持其一致性,因此 CI 程序可以更好地利用缓存。 +### 展示对话 + +如果你想,你可以在 Vue UI 中展示[对话](#prompts)。当你通过 UI 安装插件时,对话将在插件的调用步骤中展示。 + +你可以通过添加额外属性扩展 [inquirer 对象](#prompts-for-3rd-party-plugins)。他们是可选项且仅仅被 UI 使用: + +```js +// prompts.js + +module.exports = [ + { + // 基本对话属性 + name: `addExampleRoutes`, + type: 'confirm', + message: 'Add example routes?', + default: false, + // UI 关联的对话属性 + group: 'Strongly recommended', + description: 'Adds example pages, layouts and correct router config', + link: + 'https://github.com/ktsn/vue-cli-plugin-auto-routing/#vue-cli-plugin-auto-routing' + } +] +``` +现在,你将在插件调用时看到: + +![UI Prompts](/ui-prompts.png) + +### Logo + +你可以放一个 `logo.png` 文件到文件夹根目录,它将被发布到 npm。将在以下几个地方展示: +- 在搜索要安装的插件时 +- 在已安装的插件列表中 +- 在配置列表中(默认情况) +- 在添加任务的任务列表中(默认情况) + +![Plugins](/plugins.png) + +Logo 应该是方形非透明图片(理想尺寸 84*84)。 + +### 发布插件到 npm + +为了发布插件,你需要在 [npmjs.com](https://www.npmjs.com) 上注册并且全局安装 `npm`。如果这是你的第一个发布的 npm 模块,请执行 + +```bash +npm login +``` + +输入你的名字和密码。这将存储你的凭证,这样你就不必每次发布时都输入。 + +:::tip +发布插件之前,确保你为它选择了正确的名字!名字规范是 `vue-cli-plugin-`。在 [Discoverability](#discoverability) 查看更多信息 +::: + +接下来发布插件,到插件的根目录,在命令行执行下面的命令: + +```bash +npm publish +``` -[creator-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/Creator.js -[service-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/Service.js -[generator-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/GeneratorAPI.js -[commands]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/commands -[config]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/config -[plugin-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/PluginAPI.js -[prompt-modules]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/promptModules -[prompt-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/PromptModuleAPI.js +成功发布后,你应该能够使用 `vue add ` 命令将你的插件添加到使用 Vue CLI 创建的项目。 diff --git a/docs/zh/dev-guide/ui-api.md b/docs/zh/dev-guide/ui-api.md index 09bf3f7f51..c7767efd90 100644 --- a/docs/zh/dev-guide/ui-api.md +++ b/docs/zh/dev-guide/ui-api.md @@ -914,7 +914,7 @@ export default { ## 插件的 action -插件的 action 就是在 cli-ui (浏览器) 和插件 (Node.js) 直接的调用。 +插件的 action 就是在 cli-ui (浏览器) 和插件 (Node.js) 之间的调用。 > 例如,你可能有一个自定义组件里的按钮 (详见[客户端 addon](#客户端-addon)),这个按钮会通过这个 API 向服务端调用一些 Node.js 代码。 diff --git a/docs/zh/guide/browser-compatibility.md b/docs/zh/guide/browser-compatibility.md index b5a9e848cb..cf49b23605 100644 --- a/docs/zh/guide/browser-compatibility.md +++ b/docs/zh/guide/browser-compatibility.md @@ -16,9 +16,9 @@ 如果有依赖需要 polyfill,你有几种选择: -1. **如果该依赖基于一个目标环境不支持的 ES 版本撰写:** 将其添加到 `vue.config.js` 中的 [`transpileDependencies`](../config/#transpiledependencies) 选项。这会为该依赖同时开启语法语法转换和根据使用情况检测 polyfill。 +1. **如果该依赖基于一个目标环境不支持的 ES 版本撰写:** 将其添加到 `vue.config.js` 中的 [`transpileDependencies`](../config/#transpiledependencies) 选项。这会为该依赖同时开启语法转换和根据使用情况检测 polyfill。 -2. **如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill:** 你可以使用 `@vue/babel-preset-app` 的 [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) 选项预包含所需要的 polyfill。**注意 `es6.promise` 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。** +2. **如果该依赖交付了 ES5 代码并显式地列出了需要的 polyfill:** 你可以使用 `@vue/babel-preset-app` 的 [polyfills](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app#polyfills) 选项预包含所需要的 polyfill。**注意 `es.promise` 将被默认包含,因为现在的库依赖 Promise 是非常普遍的。** ``` js // babel.config.js @@ -26,8 +26,8 @@ presets: [ ['@vue/app', { polyfills: [ - 'es6.promise', - 'es6.symbol' + 'es.promise', + 'es.symbol' ] }] ] @@ -38,13 +38,13 @@ 我们推荐以这种方式添加 polyfill 而不是在源代码中直接导入它们,因为如果这里列出的 polyfill 在 `browserslist` 的目标中不需要,则它会被自动排除。 ::: -3. **如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):**请使用 `useBuiltIns: 'entry'` 然后在入口文件添加 `import '@babel/polyfill'`。这会根据 `browserslist` 目标导入**所有** polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。 +3. **如果该依赖交付 ES5 代码,但使用了 ES6+ 特性且没有显式地列出需要的 polyfill (例如 Vuetify):**请使用 `useBuiltIns: 'entry'` 然后在入口文件添加 `import 'core-js/stable'; import 'regenerator-runtime/runtime';`。这会根据 `browserslist` 目标导入**所有** polyfill,这样你就不用再担心依赖的 polyfill 问题了,但是因为包含了一些没有用到的 polyfill 所以最终的包大小可能会增加。 -更多细节可查阅 [@babel-preset/env 文档](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage)。 +更多细节可查阅 [@babel/preset-env 文档](https://new.babeljs.io/docs/en/next/babel-preset-env.html#usebuiltins-usage)。 ### 构建库或是 Web Component 时的 Polyfills -当使用 Vue CLI 来[构建一个库或是 Web Component](./build-targets.md) 时,推荐给 `@vue/babel-preset-env` 传入 `useBuiltIns: false` 选项。这能够确保你的库或是组件不包含不必要的 polyfills。通常来说,打包 polyfills 应当是最终使用你的库的应用的责任。 +当使用 Vue CLI 来[构建一个库或是 Web Component](./build-targets.md) 时,推荐给 `@vue/babel-preset-app` 传入 `useBuiltIns: false` 选项。这能够确保你的库或是组件不包含不必要的 polyfills。通常来说,打包 polyfills 应当是最终使用你的库的应用的责任。 ## 现代模式 @@ -52,7 +52,7 @@ Vue CLI 提供了一个“现代模式”帮你解决这个问题。以如下命令为生产环境构建: -``` bash +```bash vue-cli-service build --modern ``` @@ -71,13 +71,6 @@ Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 [E ::: tip 提示 ` +`) + + await run('vue-cli-service lint') +}) diff --git a/packages/@vue/cli-plugin-eslint/__tests__/ui.spec.js b/packages/@vue/cli-plugin-eslint/__tests__/ui.spec.js index 573458db46..fbab31fc12 100644 --- a/packages/@vue/cli-plugin-eslint/__tests__/ui.spec.js +++ b/packages/@vue/cli-plugin-eslint/__tests__/ui.spec.js @@ -84,7 +84,7 @@ describe('getEslintPrompts', () => { extends: 'plugin:vue/recommended', rules: { 'vue/lorem': ['error', ['asd']], // custom setting - 'vue/ipsum': 'warning' + 'vue/ipsum': 'warn' } } } @@ -146,7 +146,7 @@ describe('getEslintPrompts', () => { }) it('sets value on prompt item, if the rule was set in project\'s eslint config', () => { - expect(prompts[1].value).toBe('"warning"') + expect(prompts[1].value).toBe('"warn"') expect(prompts[2].value).toBe('["error",["asd"]]') }) diff --git a/packages/@vue/cli-plugin-eslint/eslintDeps.js b/packages/@vue/cli-plugin-eslint/eslintDeps.js new file mode 100644 index 0000000000..2cc420690f --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/eslintDeps.js @@ -0,0 +1,46 @@ +const DEPS_MAP = { + base: { + eslint: '^7.32.0', + 'eslint-plugin-vue': '^8.0.3' + }, + airbnb: { + '@vue/eslint-config-airbnb': '^6.0.0', + 'eslint-plugin-import': '^2.25.3', + 'eslint-plugin-vuejs-accessibility': '^1.1.0' + }, + prettier: { + 'eslint-config-prettier': '^8.3.0', + 'eslint-plugin-prettier': '^4.0.0', + prettier: '^2.4.1' + }, + standard: { + '@vue/eslint-config-standard': '^6.1.0', + 'eslint-plugin-import': '^2.25.3', + 'eslint-plugin-node': '^11.1.0', + 'eslint-plugin-promise': '^5.1.0' + }, + typescript: { + '@vue/eslint-config-typescript': '^9.1.0', + '@typescript-eslint/eslint-plugin': '^5.4.0', + '@typescript-eslint/parser': '^5.4.0' + } +} + +exports.DEPS_MAP = DEPS_MAP + +exports.getDeps = function (api, preset, rootOptions = {}) { + const deps = Object.assign({}, DEPS_MAP.base, DEPS_MAP[preset]) + + if (api.hasPlugin('typescript')) { + Object.assign(deps, DEPS_MAP.typescript) + } + + if (api.hasPlugin('babel') && !api.hasPlugin('typescript')) { + Object.assign(deps, { + '@babel/eslint-parser': '^7.12.16', + '@babel/core': '^7.12.16' + }) + } + + return deps +} diff --git a/packages/@vue/cli-plugin-eslint/eslintOptions.js b/packages/@vue/cli-plugin-eslint/eslintOptions.js index 5c2896a0f0..9bbb831577 100644 --- a/packages/@vue/cli-plugin-eslint/eslintOptions.js +++ b/packages/@vue/cli-plugin-eslint/eslintOptions.js @@ -1,18 +1,54 @@ -exports.config = api => { +exports.config = (api, preset, rootOptions = {}) => { const config = { root: true, env: { node: true }, extends: ['plugin:vue/essential'], + parserOptions: { + ecmaVersion: 2020 + }, rules: { - 'no-console': makeJSOnlyValue(`process.env.NODE_ENV === 'production' ? 'error' : 'off'`), - 'no-debugger': makeJSOnlyValue(`process.env.NODE_ENV === 'production' ? 'error' : 'off'`) + 'no-console': makeJSOnlyValue(`process.env.NODE_ENV === 'production' ? 'warn' : 'off'`), + 'no-debugger': makeJSOnlyValue(`process.env.NODE_ENV === 'production' ? 'warn' : 'off'`) } } - if (!api.hasPlugin('typescript')) { + + if (api.hasPlugin('babel') && !api.hasPlugin('typescript')) { config.parserOptions = { - parser: 'babel-eslint' + parser: '@babel/eslint-parser' } } + + if (preset === 'airbnb') { + config.extends.push('@vue/airbnb') + } else if (preset === 'standard') { + config.extends.push('@vue/standard') + } else if (preset === 'prettier') { + config.extends.push(...['eslint:recommended', 'plugin:prettier/recommended']) + } else { + // default + config.extends.push('eslint:recommended') + } + + if (api.hasPlugin('typescript')) { + // typically, typescript ruleset should be appended to the end of the `extends` array + // but that is not the case for prettier, as there are conflicting rules + if (preset === 'prettier') { + config.extends.pop() + config.extends.push(...['@vue/typescript/recommended', 'plugin:prettier/recommended']) + } else { + config.extends.push('@vue/typescript/recommended') + } + } + + if (rootOptions.vueVersion === '3') { + const updateConfig = cfg => + cfg.replace( + /plugin:vue\/(essential|recommended|strongly-recommended)/gi, + 'plugin:vue/vue3-$1' + ) + config.extends = config.extends.map(updateConfig) + } + return config } diff --git a/packages/@vue/cli-plugin-eslint/generator/index.js b/packages/@vue/cli-plugin-eslint/generator/index.js index 89331a35eb..7bdfb31213 100644 --- a/packages/@vue/cli-plugin-eslint/generator/index.js +++ b/packages/@vue/cli-plugin-eslint/generator/index.js @@ -1,53 +1,16 @@ const fs = require('fs') const path = require('path') -module.exports = (api, { config, lintOn = [] }, _, invoking) => { - api.assertCliVersion('^4.0.0-alpha.4') - api.assertCliServiceVersion('^4.0.0-alpha.4') - - if (typeof lintOn === 'string') { - lintOn = lintOn.split(',') - } - - const eslintConfig = require('../eslintOptions').config(api) +module.exports = (api, { config, lintOn = [] }, rootOptions, invoking) => { + const eslintConfig = require('../eslintOptions').config(api, config, rootOptions) + const devDependencies = require('../eslintDeps').getDeps(api, config, rootOptions) const pkg = { scripts: { lint: 'vue-cli-service lint' }, eslintConfig, - devDependencies: { - 'eslint': '^5.16.0', - 'eslint-plugin-vue': '^5.0.0' - } - } - - if (!api.hasPlugin('typescript')) { - pkg.devDependencies['babel-eslint'] = '^10.0.1' - } - - if (config === 'airbnb') { - eslintConfig.extends.push('@vue/airbnb') - Object.assign(pkg.devDependencies, { - '@vue/eslint-config-airbnb': '^4.0.0' - }) - } else if (config === 'standard') { - eslintConfig.extends.push('@vue/standard') - Object.assign(pkg.devDependencies, { - '@vue/eslint-config-standard': '^4.0.0' - }) - } else if (config === 'prettier') { - eslintConfig.extends.push('@vue/prettier') - Object.assign(pkg.devDependencies, { - '@vue/eslint-config-prettier': '^5.0.0', - 'eslint-plugin-prettier': '^3.1.0', - prettier: '^1.18.2' - }) - // prettier & default config do not have any style rules - // so no need to generate an editorconfig file - } else { - // default - eslintConfig.extends.push('eslint:recommended') + devDependencies } const editorConfigTemplatePath = path.resolve(__dirname, `./template/${config}/_editorconfig`) @@ -63,6 +26,10 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => { } } + if (typeof lintOn === 'string') { + lintOn = lintOn.split(',') + } + if (!lintOn.includes('save')) { pkg.vue = { lintOnSave: false // eslint-loader configured in runtime plugin @@ -71,29 +38,20 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => { if (lintOn.includes('commit')) { Object.assign(pkg.devDependencies, { - 'lint-staged': '^8.1.5' + 'lint-staged': '^11.1.2' }) pkg.gitHooks = { 'pre-commit': 'lint-staged' } - if (api.hasPlugin('typescript')) { - pkg['lint-staged'] = { - '*.{js,vue,ts}': ['vue-cli-service lint', 'git add'] - } - } else { - pkg['lint-staged'] = { - '*.{js,vue}': ['vue-cli-service lint', 'git add'] - } + const extensions = require('../eslintOptions').extensions(api) + .map(ext => ext.replace(/^\./, '')) // remove the leading `.` + pkg['lint-staged'] = { + [`*.{${extensions.join(',')}}`]: 'vue-cli-service lint' } } api.extendPackage(pkg) - // typescript support - if (api.hasPlugin('typescript')) { - applyTS(api) - } - // invoking only if (invoking) { if (api.hasPlugin('unit-mocha')) { @@ -104,16 +62,37 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => { require('@vue/cli-plugin-unit-jest/generator').applyESLint(api) } } + + // lint & fix after create to ensure files adhere to chosen config + // for older versions that do not support the `hooks` feature + try { + api.assertCliVersion('^4.0.0-beta.0') + } catch (e) { + if (config && config !== 'base') { + api.onCreateComplete(async () => { + await require('../lint')({ silent: true }, api) + }) + } + } } +// In PNPM v4, due to their implementation of the module resolution mechanism, +// put require('../lint') in the callback would raise a "Module not found" error, +// But we cannot cache the file outside the callback, +// because the node_module layout may change after the "intall additional dependencies" +// phase, thus making the cached module fail to execute. +// FIXME: at the moment we have to catch the bug and silently fail. Need to fix later. module.exports.hooks = (api) => { // lint & fix after create to ensure files adhere to chosen config - api.afterAnyInvoke(() => { - require('../lint')({ silent: true }, api) + api.afterAnyInvoke(async () => { + try { + await require('../lint')({ silent: true }, api) + } catch (e) {} }) } -const applyTS = module.exports.applyTS = api => { +// exposed for the typescript plugin +module.exports.applyTS = api => { api.extendPackage({ eslintConfig: { extends: ['@vue/typescript'], @@ -121,8 +100,6 @@ const applyTS = module.exports.applyTS = api => { parser: '@typescript-eslint/parser' } }, - devDependencies: { - '@vue/eslint-config-typescript': '^4.0.0' - } + devDependencies: require('../eslintDeps').DEPS_MAP.typescript }) } diff --git a/packages/@vue/cli-plugin-eslint/index.js b/packages/@vue/cli-plugin-eslint/index.js index 0051a0645f..c8f247ad67 100644 --- a/packages/@vue/cli-plugin-eslint/index.js +++ b/packages/@vue/cli-plugin-eslint/index.js @@ -1,64 +1,63 @@ const path = require('path') +const eslintWebpackPlugin = require('eslint-webpack-plugin') +/** @type {import('@vue/cli-service').ServicePlugin} */ module.exports = (api, options) => { if (options.lintOnSave) { const extensions = require('./eslintOptions').extensions(api) // Use loadModule to allow users to customize their ESLint dependency version. const { resolveModule, loadModule } = require('@vue/cli-shared-utils') const cwd = api.getCwd() - const eslintPkg = loadModule('eslint/package.json', cwd, true) - // eslint-loader doesn't bust cache when eslint config changes - // so we have to manually generate a cache identifier that takes the config - // into account. - const { cacheIdentifier } = api.genCacheConfig( - 'eslint-loader', + const eslintPkg = + loadModule('eslint/package.json', cwd, true) || + loadModule('eslint/package.json', __dirname, true) + + // ESLint doesn't clear the cache when you upgrade ESLint plugins (ESlint do consider config changes) + // so we have to manually generate a cache identifier that takes lock file into account. + const { cacheIdentifier, cacheDirectory } = api.genCacheConfig( + 'eslint', { - 'eslint-loader': require('eslint-loader/package.json').version, eslint: eslintPkg.version }, - [ - '.eslintrc.js', - '.eslintrc.yaml', - '.eslintrc.yml', - '.eslintrc.json', - '.eslintrc', - 'package.json' - ] + ['package.json'] ) api.chainWebpack(webpackConfig => { - webpackConfig.resolveLoader.modules.prepend( - path.join(__dirname, 'node_modules') - ) - const { lintOnSave } = options - const allWarnings = lintOnSave === true || lintOnSave === 'warning' - const allErrors = lintOnSave === 'error' + const treatAllAsWarnings = lintOnSave === true || lintOnSave === 'warning' + const treatAllAsErrors = lintOnSave === 'error' + + const failOnWarning = treatAllAsErrors + const failOnError = !treatAllAsWarnings + + /** @type {import('eslint-webpack-plugin').Options & import('eslint').ESLint.Options} */ + const eslintWebpackPluginOptions = { + // common to both plugin and ESlint + extensions, + // ESlint options + cwd, + cache: true, + cacheLocation: path.format({ + dir: cacheDirectory, + name: process.env.VUE_CLI_TEST + ? 'cache' + : cacheIdentifier, + ext: '.json' + }), + // plugin options + context: cwd, + + failOnWarning, + failOnError, - webpackConfig.module - .rule('eslint') - .pre() - .exclude - .add(/node_modules/) - .add(path.dirname(require.resolve('@vue/cli-service'))) - .end() - .test(/\.(vue|(j|t)sx?)$/) - .use('eslint-loader') - .loader('eslint-loader') - .options({ - extensions, - cache: true, - cacheIdentifier, - emitWarning: allWarnings, - // only emit errors in production mode. - emitError: allErrors, - eslintPath: path.dirname( - resolveModule('eslint/package.json', cwd) || - resolveModule('eslint/package.json', __dirname) - ), - formatter: loadModule('eslint/lib/formatters/codeframe', cwd, true) - }) + eslintPath: path.dirname( + resolveModule('eslint/package.json', cwd) || + resolveModule('eslint/package.json', __dirname) + ), + formatter: 'stylish' + } + webpackConfig.plugin('eslint').use(eslintWebpackPlugin, [eslintWebpackPluginOptions]) }) } @@ -68,19 +67,21 @@ module.exports = (api, options) => { description: 'lint and fix source files', usage: 'vue-cli-service lint [options] [...files]', options: { - '--format [formatter]': 'specify formatter (default: codeframe)', + '--format [formatter]': 'specify formatter (default: stylish)', '--no-fix': 'do not fix errors or warnings', '--no-fix-warnings': 'fix errors, but do not fix warnings', '--max-errors [limit]': 'specify number of errors to make build failed (default: 0)', '--max-warnings [limit]': - 'specify number of warnings to make build failed (default: Infinity)' + 'specify number of warnings to make build failed (default: Infinity)', + '--output-file [file_path]': + 'specify file to write report to' }, details: 'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options' }, - args => { - require('./lint')(args, api) + async args => { + await require('./lint')(args, api) } ) } diff --git a/packages/@vue/cli-plugin-eslint/lint.js b/packages/@vue/cli-plugin-eslint/lint.js index 518fb1c68f..b37690cfca 100644 --- a/packages/@vue/cli-plugin-eslint/lint.js +++ b/packages/@vue/cli-plugin-eslint/lint.js @@ -2,27 +2,31 @@ const fs = require('fs') const globby = require('globby') const renamedArrayArgs = { - ext: 'extensions', - env: 'envs', - global: 'globals', - rulesdir: 'rulePaths', - plugin: 'plugins', - 'ignore-pattern': 'ignorePattern' + ext: ['extensions'], + rulesdir: ['rulePaths'], + plugin: ['overrideConfig', 'plugins'], + 'ignore-pattern': ['overrideConfig', 'ignorePatterns'] +} + +const renamedObjectArgs = { + env: { key: ['overrideConfig', 'env'], def: true }, + global: { key: ['overrideConfig', 'globals'], def: false } } const renamedArgs = { - 'inline-config': 'allowInlineConfig', - rule: 'rules', - eslintrc: 'useEslintrc', - c: 'configFile', - config: 'configFile' + 'inline-config': ['allowInlineConfig'], + rule: ['overrideConfig', 'rules'], + eslintrc: ['useEslintrc'], + c: ['overrideConfigFile'], + config: ['overrideConfigFile'], + 'output-file': ['outputFile'] } -module.exports = function lint (args = {}, api) { +module.exports = async function lint (args = {}, api) { const path = require('path') const cwd = api.resolve('.') const { log, done, exit, chalk, loadModule } = require('@vue/cli-shared-utils') - const { CLIEngine } = loadModule('eslint', cwd, true) || require('eslint') + const { ESLint } = loadModule('eslint', cwd, true) || require('eslint') const extensions = require('./eslintOptions').extensions(api) const argsConfig = normalizeConfig(args) @@ -36,7 +40,11 @@ module.exports = function lint (args = {}, api) { const noFixWarningsPredicate = (lintResult) => lintResult.severity === 2 config.fix = config.fix && (noFixWarnings ? noFixWarningsPredicate : true) - if (!fs.existsSync(api.resolve('.eslintignore')) && !config.ignorePattern) { + if (!config.overrideConfig) { + config.overrideConfig = {} + } + + if (!fs.existsSync(api.resolve('.eslintignore')) && !config.overrideConfig.ignorePatterns) { // .eslintrc.js files (ignored by default) // However, we need to lint & fix them so as to make the default generated project's // code style consistent with user's selected eslint config. @@ -44,26 +52,59 @@ module.exports = function lint (args = {}, api) { // add our own customized ignore pattern here (in eslint, ignorePattern is // an addition to eslintignore, i.e. it can't be overridden by user), // following the principle of least astonishment. - config.ignorePattern = [ + config.overrideConfig.ignorePatterns = [ '!.*.js', '!{src,tests}/**/.*.js' ] } - - const engine = new CLIEngine(config) - - const defaultFilesToLint = [ + /** @type {import('eslint').ESLint} */ + const eslint = new ESLint(Object.fromEntries([ + + // File enumeration + 'cwd', + 'errorOnUnmatchedPattern', + 'extensions', + 'globInputPaths', + 'ignore', + 'ignorePath', + + // Linting + 'allowInlineConfig', + 'baseConfig', + 'overrideConfig', + 'overrideConfigFile', + 'plugins', + 'reportUnusedDisableDirectives', + 'resolvePluginsRelativeTo', + 'rulePaths', + 'useEslintrc', + + // Autofix + 'fix', + 'fixTypes', + + // Cache-related + 'cache', + 'cacheLocation', + 'cacheStrategy' + ].map(k => [k, config[k]]))) + + const defaultFilesToLint = [] + + for (const pattern of [ 'src', 'tests', // root config files '*.js', '.*.js' - ] - .filter(pattern => - globby - .sync(pattern, { cwd, absolute: true }) - .some(p => !engine.isPathIgnored(p)) - ) + ]) { + if ((await Promise.all(globby + .sync(pattern, { cwd, absolute: true }) + .map(p => eslint.isPathIgnored(p)))) + .some(r => !r)) { + defaultFilesToLint.push(pattern) + } + } const files = args._ && args._.length ? args._ @@ -78,41 +119,53 @@ module.exports = function lint (args = {}, api) { if (!api.invoking) { process.cwd = () => cwd } - const report = engine.executeOnFiles(files) + const resultResults = await eslint.lintFiles(files) + const reportErrorCount = resultResults.reduce((p, c) => p + c.errorCount, 0) + const reportWarningCount = resultResults.reduce((p, c) => p + c.warningCount, 0) process.cwd = processCwd - const formatter = engine.getFormatter(args.format || 'codeframe') + const formatter = await eslint.loadFormatter(args.format || 'stylish') + + if (config.outputFile) { + const outputFilePath = path.resolve(config.outputFile) + try { + fs.writeFileSync(outputFilePath, formatter.format(resultResults)) + log(`Lint results saved to ${chalk.blue(outputFilePath)}`) + } catch (err) { + log(`Error saving lint results to ${chalk.blue(outputFilePath)}: ${chalk.red(err)}`) + } + } if (config.fix) { - CLIEngine.outputFixes(report) + await ESLint.outputFixes(resultResults) } const maxErrors = argsConfig.maxErrors || 0 const maxWarnings = typeof argsConfig.maxWarnings === 'number' ? argsConfig.maxWarnings : Infinity - const isErrorsExceeded = report.errorCount > maxErrors - const isWarningsExceeded = report.warningCount > maxWarnings + const isErrorsExceeded = reportErrorCount > maxErrors + const isWarningsExceeded = reportWarningCount > maxWarnings if (!isErrorsExceeded && !isWarningsExceeded) { if (!args.silent) { - const hasFixed = report.results.some(f => f.output) + const hasFixed = resultResults.some(f => f.output) if (hasFixed) { log(`The following files have been auto-fixed:`) log() - report.results.forEach(f => { + resultResults.forEach(f => { if (f.output) { log(` ${chalk.blue(path.relative(cwd, f.filePath))}`) } }) log() } - if (report.warningCount || report.errorCount) { - console.log(formatter(report.results)) + if (reportWarningCount || reportErrorCount) { + console.log(formatter.format(resultResults)) } else { done(hasFixed ? `All lint errors auto-fixed.` : `No lint errors found!`) } } } else { - console.log(formatter(report.results)) + console.log(formatter.format(resultResults)) if (isErrorsExceeded && typeof argsConfig.maxErrors === 'number') { log(`Eslint found too many errors (maximum: ${argsConfig.maxErrors}).`) } @@ -127,14 +180,35 @@ function normalizeConfig (args) { const config = {} for (const key in args) { if (renamedArrayArgs[key]) { - config[renamedArrayArgs[key]] = args[key].split(',') + applyConfig(renamedArrayArgs[key], args[key].split(',')) + } else if (renamedObjectArgs[key]) { + const obj = arrayToBoolObject(args[key].split(','), renamedObjectArgs[key].def) + applyConfig(renamedObjectArgs[key].key, obj) } else if (renamedArgs[key]) { - config[renamedArgs[key]] = args[key] + applyConfig(renamedArgs[key], args[key]) } else if (key !== '_') { config[camelize(key)] = args[key] } } return config + + function applyConfig ([...keyPaths], value) { + let targetConfig = config + const lastKey = keyPaths.pop() + for (const k of keyPaths) { + targetConfig = targetConfig[k] || (targetConfig[k] = {}) + } + targetConfig[lastKey] = value + } + + function arrayToBoolObject (array, defaultBool) { + const object = {} + for (const element of array) { + const [key, value] = element.split(':') + object[key] = value != null ? value === 'true' : defaultBool + } + return object + } } function camelize (str) { diff --git a/packages/@vue/cli-plugin-eslint/migrator/index.js b/packages/@vue/cli-plugin-eslint/migrator/index.js index a1915af9d3..3417048267 100644 --- a/packages/@vue/cli-plugin-eslint/migrator/index.js +++ b/packages/@vue/cli-plugin-eslint/migrator/index.js @@ -1,26 +1,91 @@ -module.exports = (api) => { +const { semver } = require('@vue/cli-shared-utils') + +/** @param {import('@vue/cli/lib/MigratorAPI')} api MigratorAPI */ +module.exports = async (api) => { + const pkg = require(api.resolve('package.json')) + + let localESLintRange = pkg.devDependencies.eslint + // if project is scaffolded by Vue CLI 3.0.x or earlier, // the ESLint dependency (ESLint v4) is inside @vue/cli-plugin-eslint; // in Vue CLI v4 it should be extracted to the project dependency list. - if (api.fromVersion('^3')) { - const pkg = require(api.resolve('package.json')) - const hasESLint = [ - 'dependencies', - 'devDependencies', - 'peerDependencies', - 'optionalDependencies' - ].some(depType => - Object.keys(pkg[depType] || {}).includes('eslint') - ) - - if (!hasESLint) { - api.extendPackage({ - devDependencies: { - eslint: '^4.19.1' - } - }) + if (api.fromVersion('^3') && !localESLintRange) { + localESLintRange = '^4.19.1' + api.extendPackage({ + devDependencies: { + eslint: localESLintRange, + '@babel/eslint-parser': '^7.12.16', + 'eslint-plugin-vue': '^4.5.0' + } + }) + } + + const localESLintMajor = semver.major( + semver.maxSatisfying(['4.99.0', '5.99.0', '6.99.0', '7.99.0'], localESLintRange) || + // in case the user does not specify a typical caret range; + // it is used as **fallback** because the user may have not previously + // installed eslint yet, such as in the case that they are from v3.0.x + // eslint-disable-next-line node/no-extraneous-require + require('eslint/package.json').version + ) + + if (localESLintMajor > 6) { + return + } + + const { getDeps } = require('../eslintDeps') + + const newDeps = getDeps(api) + if (pkg.devDependencies['@vue/eslint-config-airbnb']) { + Object.assign(newDeps, getDeps(api, 'airbnb')) + } + if (pkg.devDependencies['@vue/eslint-config-standard']) { + Object.assign(newDeps, getDeps(api, 'standard')) + } + if (pkg.devDependencies['@vue/eslint-config-prettier']) { + Object.assign(newDeps, getDeps(api, 'prettier')) + } + + const fields = { devDependencies: newDeps } + + if (newDeps['@babel/core'] && newDeps['@babel/eslint-parser']) { + Reflect.deleteProperty(api.generator.pkg.devDependencies, 'babel-eslint') + + const minSupportedBabelCoreVersion = '>=7.2.0' + const localBabelCoreVersion = pkg.devDependencies['@babel/core'] + + if (localBabelCoreVersion && + semver.satisfies( + localBabelCoreVersion, + minSupportedBabelCoreVersion + )) { + Reflect.deleteProperty(newDeps, '@babel/core') + } + + fields.eslintConfig = { + parserOptions: { + parser: '@babel/eslint-parser' + } } + } + + api.extendPackage(fields, { warnIncompatibleVersions: false }) - // TODO: add a prompt for users to optionally upgrade their eslint configs to a new major version + // in case anyone's upgrading from the legacy `typescript-eslint-parser` + if (api.hasPlugin('typescript')) { + api.extendPackage({ + eslintConfig: { + parserOptions: { + parser: '@typescript-eslint/parser' + } + } + }) } + + api.exitLog(`ESLint upgraded from v${localESLintMajor}. to v7\n`) + + // TODO: + // transform `@vue/prettier` to `eslint:recommended` + `plugin:prettier/recommended` + // remove `@vue/prettier/@typescript-eslint` + // transform `@vue/typescript` to `@vue/typescript/recommended` and also fix prettier compatibility for it } diff --git a/packages/@vue/cli-plugin-eslint/package.json b/packages/@vue/cli-plugin-eslint/package.json index d671e96ce1..26f227844a 100644 --- a/packages/@vue/cli-plugin-eslint/package.json +++ b/packages/@vue/cli-plugin-eslint/package.json @@ -1,6 +1,6 @@ { "name": "@vue/cli-plugin-eslint", - "version": "4.0.0-rc.4", + "version": "5.0.8", "description": "eslint plugin for vue-cli", "main": "index.js", "repository": { @@ -23,14 +23,14 @@ "access": "public" }, "dependencies": { - "@vue/cli-shared-utils": "^4.0.0-rc.4", - "eslint-loader": "^2.1.2", - "globby": "^9.2.0", - "webpack": "^4.0.0", + "@vue/cli-shared-utils": "^5.0.8", + "eslint-webpack-plugin": "^3.1.0", + "globby": "^11.0.2", + "webpack": "^5.54.0", "yorkie": "^2.0.0" }, "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0", - "eslint": ">= 1.6.0" + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0", + "eslint": ">=7.5.0" } } diff --git a/packages/@vue/cli-plugin-eslint/ui/configDescriptor.js b/packages/@vue/cli-plugin-eslint/ui/configDescriptor.js index afbdd1080d..a8ec57ddf6 100644 --- a/packages/@vue/cli-plugin-eslint/ui/configDescriptor.js +++ b/packages/@vue/cli-plugin-eslint/ui/configDescriptor.js @@ -10,7 +10,7 @@ const CATEGORIES = [ const DEFAULT_CATEGORY = 'essential' const RULE_SETTING_OFF = 'off' const RULE_SETTING_ERROR = 'error' -const RULE_SETTING_WARNING = 'warning' +const RULE_SETTING_WARNING = 'warn' const RULE_SETTINGS = [RULE_SETTING_OFF, RULE_SETTING_ERROR, RULE_SETTING_WARNING] const defaultChoices = [ diff --git a/packages/@vue/cli-plugin-pwa/README.md b/packages/@vue/cli-plugin-pwa/README.md index abf24578b9..ed406900fd 100644 --- a/packages/@vue/cli-plugin-pwa/README.md +++ b/packages/@vue/cli-plugin-pwa/README.md @@ -15,7 +15,7 @@ file, or the `"vue"` field in `package.json`. - **pwa.workboxPluginMode** - This allows you to the choose between the two modes supported by the underlying + This allows you to choose between the two modes supported by the underlying [`workbox-webpack-plugin`](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin). - `'GenerateSW'` (default), will lead to a new service worker file being created @@ -69,7 +69,7 @@ file, or the `"vue"` field in `package.json`. - Default: `'manifest.json'` - The path of app’s manifest. + The path of app’s manifest. If the path is an URL, the plugin won't generate a manifest.json in the dist directory during the build. - **pwa.manifestOptions** @@ -84,12 +84,19 @@ file, or the `"vue"` field in `package.json`. - display: `'standalone'` - theme_color: `pwa.themeColor` +- **pwa.manifestCrossorigin** + + - Default: `undefined` + + Value for `crossorigin` attribute in manifest link tag in the generated HTML. You may need to set this if your PWA is behind an authenticated proxy. See [cross-origin values](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-crossorigin) for more details. + - **pwa.iconPaths** - Defaults: ```js { + faviconSVG: 'img/icons/favicon.svg', favicon32: 'img/icons/favicon-32x32.png', favicon16: 'img/icons/favicon-16x16.png', appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', @@ -98,7 +105,7 @@ file, or the `"vue"` field in `package.json`. } ``` - Change these values to use different paths for your icons. + Change these values to use different paths for your icons. As of v4.3.0, you can use `null` as a value and that icon will not be included. ### Example Configuration @@ -126,7 +133,7 @@ module.exports = { ## Installing in an Already Created Project -``` sh +```bash vue add pwa ``` diff --git a/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js b/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js index 070be9e4a7..56054308cc 100644 --- a/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js +++ b/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js @@ -30,16 +30,16 @@ test('pwa', async () => { const index = await project.read('dist/index.html') // should split and preload app.js & vendor.js - expect(index).toMatch(/]+js\/app[^>]+\.js rel=preload as=script>/) - expect(index).toMatch(/]+js\/chunk-vendors[^>]+\.js rel=preload as=script>/) + // expect(index).toMatch(/]+js\/app[^>]+\.js" rel="preload" as="script">/) + // expect(index).toMatch(/]+js\/chunk-vendors[^>]+\.js" rel="preload" as="script">/) // should preload css - expect(index).toMatch(/]+app[^>]+\.css rel=preload as=style>/) + // expect(index).toMatch(/]+app[^>]+\.css" rel="preload" as="style">/) // PWA specific directives - expect(index).toMatch(``) + expect(index).toMatch(``) // favicon is not minified because it's technically a comment expect(index).toMatch(``) - expect(index).toMatch(``) + expect(index).toMatch(``) // should import service worker script const main = await project.read('src/main.js') @@ -59,7 +59,7 @@ test('pwa', async () => { browser = launched.browser // workbox plugin fetches scripts from CDN so it takes a while... - await new Promise(r => setTimeout(r, process.env.CI ? 5000 : 2000)) + await new Promise(resolve => setTimeout(resolve, process.env.CI ? 5000 : 2000)) const logs = launched.logs expect(logs.some(msg => msg.match(/Content has been cached for offline use/))).toBe(true) expect(logs.some(msg => msg.match(/App is being served from cache by a service worker/))).toBe(true) diff --git a/packages/@vue/cli-plugin-pwa/generator/index.js b/packages/@vue/cli-plugin-pwa/generator/index.js index fe1f48c2f0..fd3ca254a0 100644 --- a/packages/@vue/cli-plugin-pwa/generator/index.js +++ b/packages/@vue/cli-plugin-pwa/generator/index.js @@ -1,7 +1,7 @@ module.exports = api => { api.extendPackage({ dependencies: { - 'register-service-worker': '^1.6.2' + 'register-service-worker': '^1.7.2' } }) api.injectImports(api.entryFile, `import './registerServiceWorker'`) diff --git a/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-192x192.png b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-192x192.png new file mode 100644 index 0000000000..791e9c8c2c Binary files /dev/null and b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-192x192.png differ diff --git a/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-512x512.png b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-512x512.png new file mode 100644 index 0000000000..5f2098ed27 Binary files /dev/null and b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/android-chrome-maskable-512x512.png differ diff --git a/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/safari-pinned-tab.svg b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/safari-pinned-tab.svg index 732afd8eb0..e44c0d5b0f 100755 --- a/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/safari-pinned-tab.svg +++ b/packages/@vue/cli-plugin-pwa/generator/template/public/img/icons/safari-pinned-tab.svg @@ -1,149 +1,3 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - + + diff --git a/packages/@vue/cli-plugin-pwa/index.js b/packages/@vue/cli-plugin-pwa/index.js index 462fa81656..89ac894134 100644 --- a/packages/@vue/cli-plugin-pwa/index.js +++ b/packages/@vue/cli-plugin-pwa/index.js @@ -1,4 +1,20 @@ +const fs = require('fs') +const { chalk, warn } = require('@vue/cli-shared-utils') + module.exports = (api, options) => { + const userOptions = options.pwa || {} + + const manifestPath = api.resolve('public/manifest.json') + if (fs.existsSync(manifestPath)) { + if (!userOptions.manifestOptions) { + userOptions.manifestOptions = require(manifestPath) + } else { + warn( + `The ${chalk.red('public/manifest.json')} file will be ignored in favor of ${chalk.cyan('pwa.manifestOptions')}` + ) + } + } + api.chainWebpack(webpackConfig => { const target = process.env.VUE_CLI_BUILD_TARGET if (target && target !== 'app') { @@ -6,7 +22,6 @@ module.exports = (api, options) => { } const name = api.service.pkg.name - const userOptions = options.pwa || {} // the pwa plugin hooks on to html-webpack-plugin // and injects icons, manifest links & other PWA related tags into @@ -39,9 +54,9 @@ module.exports = (api, options) => { ] } - const defaultGenerateSWOptions = workboxPluginMode === 'GenerateSW' ? { - cacheId: name - } : {} + const defaultGenerateSWOptions = workboxPluginMode === 'GenerateSW' + ? { cacheId: name } + : {} const workBoxConfig = Object.assign(defaultOptions, defaultGenerateSWOptions, userOptions.workboxOptions) diff --git a/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js b/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js index 8f114e6c45..ba86f2a23b 100644 --- a/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js +++ b/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js @@ -1,3 +1,5 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin') + const ID = 'vue-cli:pwa-html-plugin' const defaults = { @@ -23,6 +25,18 @@ const defaultManifest = { 'src': './img/icons/android-chrome-512x512.png', 'sizes': '512x512', 'type': 'image/png' + }, + { + 'src': './img/icons/android-chrome-maskable-192x192.png', + 'sizes': '192x192', + 'type': 'image/png', + 'purpose': 'maskable' + }, + { + 'src': './img/icons/android-chrome-maskable-512x512.png', + 'sizes': '512x512', + 'type': 'image/png', + 'purpose': 'maskable' } ], start_url: '.', @@ -31,6 +45,7 @@ const defaultManifest = { } const defaultIconPaths = { + faviconSVG: 'img/icons/favicon.svg', favicon32: 'img/icons/favicon-32x32.png', favicon16: 'img/icons/favicon-16x16.png', appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', @@ -47,13 +62,13 @@ module.exports = class HtmlPwaPlugin { apply (compiler) { compiler.hooks.compilation.tap(ID, compilation => { - compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(ID, (data, cb) => { + HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(ID, (data, cb) => { // wrap favicon in the base template with IE only comment data.html = data.html.replace(/]+>/, '') cb(null, data) }) - compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, (data, cb) => { + HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, (data, cb) => { const { name, themeColor, @@ -69,39 +84,57 @@ module.exports = class HtmlPwaPlugin { const assetsVersionStr = assetsVersion ? `?v=${assetsVersion}` : '' - data.head.push( - // Favicons - makeTag('link', { + // Favicons + if (iconPaths.faviconSVG != null) { + data.headTags.push(makeTag('link', { + rel: 'icon', + type: 'image/svg+xml', + href: getTagHref(publicPath, iconPaths.faviconSVG, assetsVersionStr) + })) + } + if (iconPaths.favicon32 != null) { + data.headTags.push(makeTag('link', { rel: 'icon', type: 'image/png', sizes: '32x32', - href: `${publicPath}${iconPaths.favicon32}${assetsVersionStr}` - }), - makeTag('link', { + href: getTagHref(publicPath, iconPaths.favicon32, assetsVersionStr) + })) + } + if (iconPaths.favicon16 != null) { + data.headTags.push(makeTag('link', { rel: 'icon', type: 'image/png', sizes: '16x16', - href: `${publicPath}${iconPaths.favicon16}${assetsVersionStr}` - }), + href: getTagHref(publicPath, iconPaths.favicon16, assetsVersionStr) + })) + } - // Add to home screen for Android and modern mobile browsers + // Add to home screen for Android and modern mobile browsers + data.headTags.push( makeTag('link', manifestCrossorigin ? { rel: 'manifest', - href: `${publicPath}${manifestPath}${assetsVersionStr}`, + href: getTagHref(publicPath, manifestPath, assetsVersionStr), crossorigin: manifestCrossorigin } : { rel: 'manifest', - href: `${publicPath}${manifestPath}${assetsVersionStr}` + href: getTagHref(publicPath, manifestPath, assetsVersionStr) } - ), - makeTag('meta', { - name: 'theme-color', - content: themeColor - }), + ) + ) - // Add to home screen for Safari on iOS + if (themeColor != null) { + data.headTags.push( + makeTag('meta', { + name: 'theme-color', + content: themeColor + }) + ) + } + + // Add to home screen for Safari on iOS + data.headTags.push( makeTag('meta', { name: 'apple-mobile-web-app-capable', content: appleMobileWebAppCapable @@ -113,33 +146,43 @@ module.exports = class HtmlPwaPlugin { makeTag('meta', { name: 'apple-mobile-web-app-title', content: name - }), - makeTag('link', { + }) + ) + if (iconPaths.appleTouchIcon != null) { + data.headTags.push(makeTag('link', { rel: 'apple-touch-icon', - href: `${publicPath}${iconPaths.appleTouchIcon}${assetsVersionStr}` - }), - makeTag('link', { + href: getTagHref(publicPath, iconPaths.appleTouchIcon, assetsVersionStr) + })) + } + if (iconPaths.maskIcon != null) { + data.headTags.push(makeTag('link', { rel: 'mask-icon', - href: `${publicPath}${iconPaths.maskIcon}${assetsVersionStr}`, + href: getTagHref(publicPath, iconPaths.maskIcon, assetsVersionStr), color: themeColor - }), + })) + } - // Add to home screen for Windows - makeTag('meta', { + // Add to home screen for Windows + if (iconPaths.msTileImage != null) { + data.headTags.push(makeTag('meta', { name: 'msapplication-TileImage', - content: `${publicPath}${iconPaths.msTileImage}${assetsVersionStr}` - }), - makeTag('meta', { - name: 'msapplication-TileColor', - content: msTileColor - }) - ) + content: getTagHref(publicPath, iconPaths.msTileImage, assetsVersionStr) + })) + } + if (msTileColor != null) { + data.headTags.push( + makeTag('meta', { + name: 'msapplication-TileColor', + content: msTileColor + }) + ) + } cb(null, data) }) }) - compiler.hooks.emit.tapAsync(ID, (data, cb) => { + if (!isHrefAbsoluteUrl(this.options.manifestPath)) { const { name, themeColor, @@ -154,19 +197,37 @@ module.exports = class HtmlPwaPlugin { const outputManifest = JSON.stringify( Object.assign(publicOptions, defaultManifest, manifestOptions) ) - data.assets[manifestPath] = { + const manifestAsset = { source: () => outputManifest, size: () => outputManifest.length } - cb(null, data) - }) + + compiler.hooks.compilation.tap(ID, compilation => { + compilation.hooks.processAssets.tap( + { name: ID, stage: 'PROCESS_ASSETS_STAGE_ADDITIONS' }, + assets => { assets[manifestPath] = manifestAsset } + ) + }) + } } } -function makeTag (tagName, attributes, closeTag = false) { +function makeTag (tagName, attributes, voidTag = true) { return { tagName, - closeTag, + voidTag, attributes } } + +function getTagHref (publicPath, href, assetsVersionStr) { + let tagHref = `${href}${assetsVersionStr}` + if (!isHrefAbsoluteUrl(href)) { + tagHref = `${publicPath}${tagHref}` + } + return tagHref +} + +function isHrefAbsoluteUrl (href) { + return /(http(s?)):\/\//gi.test(href) +} diff --git a/packages/@vue/cli-plugin-pwa/lib/noopServiceWorker.js b/packages/@vue/cli-plugin-pwa/lib/noopServiceWorker.js index d4f06e7136..aa7449e1ad 100644 --- a/packages/@vue/cli-plugin-pwa/lib/noopServiceWorker.js +++ b/packages/@vue/cli-plugin-pwa/lib/noopServiceWorker.js @@ -1,3 +1,4 @@ +/* eslint-disable-next-line no-redeclare */ /* global self */ // This service worker file is effectively a 'no-op' that will reset any @@ -11,6 +12,8 @@ self.addEventListener('install', () => self.skipWaiting()) +self.addEventListener('fetch', () => {}) + self.addEventListener('activate', () => { self.clients.matchAll({ type: 'window' }).then(windowClients => { for (const windowClient of windowClients) { diff --git a/packages/@vue/cli-plugin-pwa/package.json b/packages/@vue/cli-plugin-pwa/package.json index b3582a00d2..1767c8f119 100644 --- a/packages/@vue/cli-plugin-pwa/package.json +++ b/packages/@vue/cli-plugin-pwa/package.json @@ -1,6 +1,6 @@ { "name": "@vue/cli-plugin-pwa", - "version": "4.0.0-rc.4", + "version": "5.0.8", "description": "pwa plugin for vue-cli", "main": "index.js", "repository": { @@ -23,14 +23,15 @@ "access": "public" }, "dependencies": { - "@vue/cli-shared-utils": "^4.0.0-rc.4", - "webpack": "^4.0.0", - "workbox-webpack-plugin": "^4.3.1" + "@vue/cli-shared-utils": "^5.0.8", + "html-webpack-plugin": "^5.1.0", + "webpack": "^5.54.0", + "workbox-webpack-plugin": "^6.1.0" }, "devDependencies": { - "register-service-worker": "^1.6.2" + "register-service-worker": "^1.7.2" }, "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0-0" + "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" } } diff --git a/packages/@vue/cli-plugin-pwa/ui.js b/packages/@vue/cli-plugin-pwa/ui.js index ec4b45cf76..cc2592c220 100644 --- a/packages/@vue/cli-plugin-pwa/ui.js +++ b/packages/@vue/cli-plugin-pwa/ui.js @@ -15,7 +15,13 @@ module.exports = api => { json: ['public/manifest.json'] } }, - onRead: ({ data, cwd }) => { + onRead: ({ data }) => { + // Dirty hack here: only in onRead can we delete files from the original data. + // Remove (or, don't create the file) manifest.json if no actual content in it. + if (!data.manifest || !Object.keys(data.manifest).length) { + delete data.manifest + } + return { prompts: [ { @@ -23,7 +29,7 @@ module.exports = api => { type: 'list', message: 'org.vue.pwa.config.pwa.workboxPluginMode.message', description: 'org.vue.pwa.config.pwa.workboxPluginMode.description', - link: 'https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin#which_plugin_to_use', + link: 'https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/#which-plugin-to-use', default: 'GenerateSW', value: data.vue && data.vue.pwa && data.vue.pwa.workboxPluginMode, choices: [ @@ -58,7 +64,12 @@ module.exports = api => { message: 'org.vue.pwa.config.pwa.backgroundColor.message', description: 'org.vue.pwa.config.pwa.backgroundColor.description', default: '#000000', - value: data.manifest && data.manifest.background_color, + value: + (data.vue && + data.vue.pwa && + data.vue.pwa.manifestOptions && + data.vue.pwa.manifestOptions.background_color) || + (data.manifest && data.manifest.background_color), skipSave: true }, { @@ -82,12 +93,12 @@ module.exports = api => { type: 'list', message: 'org.vue.pwa.config.pwa.manifestCrossorigin.message', description: 'org.vue.pwa.config.pwa.manifestCrossorigin.description', - default: undefined, + default: null, value: data.vue && data.vue.pwa && data.vue.pwa.manifestCrossorigin, choices: [ { name: 'none', - value: undefined + value: null }, { name: 'anonymous', @@ -102,35 +113,42 @@ module.exports = api => { ] } }, - onWrite: async ({ onWriteApi, prompts, cwd }) => { + onWrite: async ({ api: onWriteApi, data, prompts }) => { const result = {} for (const prompt of prompts.filter(p => !p.raw.skipSave)) { result[`pwa.${prompt.id}`] = await onWriteApi.getAnswer(prompt.id) } - onWriteApi.setData('vue', result) - - // Update app manifest - const name = result['name'] - if (name) { - onWriteApi.setData('manifest', { - name, - short_name: name - }) + const backgroundColor = await onWriteApi.getAnswer('backgroundColor') + if (!data.manifest && backgroundColor) { + result['pwa.manifestOptions.background_color'] = backgroundColor } - const themeColor = result['themeColor'] - if (themeColor) { - onWriteApi.setData('manifest', { - theme_color: themeColor - }) - } + onWriteApi.setData('vue', result) - const backgroundColor = await onWriteApi.getAnswer('backgroundColor') - if (backgroundColor) { - onWriteApi.setData('manifest', { - background_color: backgroundColor - }) + // Update app manifest (only when there's a manifest.json file, + // otherwise it will be inferred from options in vue.config.js) + if (data.manifest) { + const name = result.name + if (name) { + onWriteApi.setData('manifest', { + name, + short_name: name + }) + } + + const themeColor = result.themeColor + if (themeColor) { + onWriteApi.setData('manifest', { + theme_color: themeColor + }) + } + + if (backgroundColor) { + onWriteApi.setData('manifest', { + background_color: backgroundColor + }) + } } } }) diff --git a/packages/@vue/cli-plugin-router/README.md b/packages/@vue/cli-plugin-router/README.md index 7d79da9dab..48fb1af544 100644 --- a/packages/@vue/cli-plugin-router/README.md +++ b/packages/@vue/cli-plugin-router/README.md @@ -4,6 +4,6 @@ ## Installing in an Already Created Project -``` sh +```bash vue add router ``` diff --git a/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js b/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js index baac1337b6..13b4af44a0 100644 --- a/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js +++ b/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js @@ -9,11 +9,11 @@ test('base', async () => { expect(files['src/router/index.js']).toBeTruthy() expect(files['src/router/index.js']).not.toMatch('history') - expect(files['src/views/About.vue']).toBeTruthy() - expect(files['src/views/Home.vue']).toBeTruthy() + expect(files['src/views/AboutView.vue']).toBeTruthy() + expect(files['src/views/HomeView.vue']).toBeTruthy() expect(files['src/App.vue']).toMatch('Home') expect(files['src/App.vue']).not.toMatch(' + `) + + const { stdout } = await project.run('vue-cli-service build') + expect(stdout).toMatch('Build complete.') +}) diff --git a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginTSLint.spec.js b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginTSLint.spec.js deleted file mode 100644 index a487e70241..0000000000 --- a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginTSLint.spec.js +++ /dev/null @@ -1,115 +0,0 @@ -jest.setTimeout(30000) - -const create = require('@vue/cli-test-utils/createTestProject') - -test('should work', async () => { - const project = await create('ts-tslint', { - plugins: { - '@vue/cli-plugin-typescript': { - tsLint: true - } - } - }) - const { read, write, run } = project - const main = await read('src/main.ts') - expect(main).toMatch(';') - const app = await read('src/App.vue') - expect(main).toMatch(';') - // remove semicolons - const updatedMain = main.replace(/;/g, '') - await write('src/main.ts', updatedMain) - // for Vue file, only remove semis in script section - const updatedApp = app.replace(//, $ => { - return $.replace(/;/g, '') - }) - await write('src/App.vue', updatedApp) - // lint - await run('vue-cli-service lint') - expect(await read('src/main.ts')).toMatch(';') - - const lintedApp = await read('src/App.vue') - expect(lintedApp).toMatch(';') - // test if tslint is fixing vue files properly - expect(lintedApp).toBe(app) -}) - -test('should not fix with --no-fix option', async () => { - const project = await create('ts-tslint-nofix', { - plugins: { - '@vue/cli-plugin-typescript': { - tsLint: true - } - } - }) - const { read, write, run } = project - const main = await read('src/main.ts') - expect(main).toMatch(';') - const app = await read('src/App.vue') - expect(main).toMatch(';') - // remove semicolons - const updatedMain = main.replace(/;/g, '') - await write('src/main.ts', updatedMain) - // for Vue file, only remove semis in script section - const updatedApp = app.replace(//, $ => { - return $.replace(/;/g, '') - }) - await write('src/App.vue', updatedApp) - - // lint with no fix should fail - try { - await run('vue-cli-service lint --no-fix') - } catch (e) { - expect(e.code).toBe(1) - expect(e.failed).toBeTruthy() - } - - // files should not have been fixed - expect(await read('src/main.ts')).not.toMatch(';') - expect((await read('src/App.vue')).match(//)[1]).not.toMatch(';') -}) - -test('should ignore issues in node_modules', async () => { - const project = await create('ts-lint-node_modules', { - plugins: { - '@vue/cli-plugin-typescript': { - tsLint: true - } - } - }) - - const { read, write, run } = project - const main = await read('src/main.ts') - - // update file to not match tslint spec and dump it into the node_modules directory - const updatedMain = main.replace(/;/g, '') - await write('node_modules/bad.ts', updatedMain) - - // lint - await run('vue-cli-service lint') - expect(await read('node_modules/bad.ts')).toMatch(updatedMain) -}) - -test('should be able to fix mixed line endings', async () => { - const project = await create('ts-lint-mixed-line-endings', { - plugins: { - '@vue/cli-plugin-typescript': { - tsLint: true - } - } - }) - - const { write, run } = project - - const b64 = 'PHRlbXBsYXRlPjwvdGVtcGxhdGU+DQoNCjxzY3JpcHQgbGFuZz0idHMiPg0KZXhwb3J0IGRlZmF1bHQgY2xhc3MgVGVzdCAgew0KICBnZXQgYXNzaWduZWUoKSB7DQogICAgdmFyIGl0ZW1zOnt0ZXh0OnN0cmluZzsgdmFsdWU6c3RyaW5nIHwgbnVtYmVyIHwgbnVsbH1bXSA9IFtdOw0KICAgIHJldHVybiBpdGVtczsNCiAgfQ0KDQp9DQo8L3NjcmlwdD4NCg0K' - const buf = Buffer.from(b64, 'base64') - - await write('src/bad.vue', buf) - - // Try twice to fix the file. - // For now, it will fail the first time, which corresponds to the behaviour of tslint. - try { - await run('vue-cli-service lint -- src/bad.vue') - } catch (e) { } - - await run('vue-cli-service lint -- src/bad.vue') -}) diff --git a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginUnit.spec.js b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginUnit.spec.js index bcd455e6eb..044539b3d3 100644 --- a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginUnit.spec.js +++ b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginUnit.spec.js @@ -2,16 +2,6 @@ jest.setTimeout(40000) const create = require('@vue/cli-test-utils/createTestProject') -test('mocha', async () => { - const project = await create('ts-unit-mocha', { - plugins: { - '@vue/cli-plugin-typescript': {}, - '@vue/cli-plugin-unit-mocha': {} - } - }) - await project.run(`vue-cli-service test:unit`) -}) - test('jest', async () => { const project = await create('ts-unit-jest', { plugins: { diff --git a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js new file mode 100644 index 0000000000..59f2a700fd --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js @@ -0,0 +1,13 @@ +jest.setTimeout(300000) + +const { assertServe, assertBuild } = require('./tsPlugin.helper') + +const options = { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-typescript': {} + } +} + +assertServe('ts-vue-3-serve', options, true) +assertBuild('ts-vue-3-build', options, undefined, true) diff --git a/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.input.ts b/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.input.ts new file mode 100644 index 0000000000..798e8fcfac --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.input.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { defineComponent } from 'vue'; + const component: ReturnType; + export default component; +} diff --git a/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.output.ts b/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.output.ts new file mode 100644 index 0000000000..506bf2e5c9 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/codemods/__testfixtures__/shims-vue.output.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/packages/@vue/cli-plugin-typescript/codemods/__tests__/migrateComponentType.spec.js b/packages/@vue/cli-plugin-typescript/codemods/__tests__/migrateComponentType.spec.js new file mode 100644 index 0000000000..ebf0a9297b --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/codemods/__tests__/migrateComponentType.spec.js @@ -0,0 +1,5 @@ +jest.autoMockOff() + +const { defineTest } = require('jscodeshift/dist/testUtils') + +defineTest(__dirname, 'migrateComponentType', null, 'shims-vue', { parser: 'ts' }) diff --git a/packages/@vue/cli-plugin-typescript/codemods/migrateComponentType.js b/packages/@vue/cli-plugin-typescript/codemods/migrateComponentType.js new file mode 100644 index 0000000000..3c1091a630 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/codemods/migrateComponentType.js @@ -0,0 +1,102 @@ +// `shims-vue.d.ts` for Vue 3, generated by CLI 4.5.0-4.5.6, uses the following declaration: +// `component: ReturnType` + +// So needs to update to: +// `component: DefineComponent` + +module.exports = function migrateComponentType (file, api) { + const j = api.jscodeshift + const root = j(file.source) + + const useDoubleQuote = root.find(j.StringLiteral).some(({ node }) => node.extra.raw.startsWith('"')) + + const tsmodule = root.find(j.TSModuleDeclaration, { + id: { + value: '*.vue' + } + }) + + const componentDecl = tsmodule.find(j.VariableDeclarator, { + id: { + name: 'component', + typeAnnotation: { + typeAnnotation: { + typeName: { + name: 'ReturnType' + }, + typeParameters: { + params: { + 0: { + exprName: { + name: 'defineComponent' + } + } + } + } + } + } + } + }) + + if (componentDecl.length !== 1) { + return file.source + } + + // update the component type + componentDecl.forEach(({ node }) => { + node.id.typeAnnotation = j.tsTypeAnnotation( + j.tsTypeReference( + j.identifier('DefineComponent'), + j.tsTypeParameterInstantiation([ + j.tsTypeLiteral([]), + j.tsTypeLiteral([]), + j.tsAnyKeyword() + ]) + ) + ) + }) + + // insert DefineComponent type import + const importDeclFromVue = tsmodule.find(j.ImportDeclaration, { + source: { + value: 'vue' + } + }) + importDeclFromVue + .get(0) + .node.specifiers.push(j.importSpecifier(j.identifier('DefineComponent'))) + + // remove defineComponent import if unused + const defineComponentUsages = tsmodule + .find(j.Identifier, { name: 'defineComponent' }) + .filter((identifierPath) => { + const parent = identifierPath.parent.node + + // Ignore the import specifier + if ( + j.ImportDefaultSpecifier.check(parent) || + j.ImportSpecifier.check(parent) || + j.ImportNamespaceSpecifier.check(parent) + ) { + return false + } + + return true + }) + if (defineComponentUsages.length === 0) { + tsmodule + .find(j.ImportSpecifier, { + local: { + name: 'defineComponent' + } + }) + .remove() + } + + return root.toSource({ + lineTerminator: '\n', + quote: useDoubleQuote ? 'double' : 'single' + }) +} + +module.exports.parser = 'ts' diff --git a/packages/@vue/cli-plugin-typescript/generator/convert.js b/packages/@vue/cli-plugin-typescript/generator/convert.js index c024aaaa8e..f056d4c3fd 100644 --- a/packages/@vue/cli-plugin-typescript/generator/convert.js +++ b/packages/@vue/cli-plugin-typescript/generator/convert.js @@ -1,8 +1,11 @@ -module.exports = (api, { tsLint = false, convertJsToTs = true } = {}) => { +module.exports = (api, { convertJsToTs = true } = {}) => { const jsRE = /\.js$/ - const excludeRE = /^tests\/e2e\/|(\.config|rc)\.js$/ - const convertLintFlags = require('../lib/convertLintFlags') - api.postProcessFiles(files => { + let excludeRE = /^tests\/e2e\/|(\.config|rc)\.js$/ + + if (api.hasPlugin('e2e-webdriverio')) { + excludeRE = /(\.config|rc)\.js$/ + } + api.postProcessFiles((files) => { if (convertJsToTs) { // delete all js files that have a ts file of the same name // and simply rename other js files to ts @@ -10,10 +13,7 @@ module.exports = (api, { tsLint = false, convertJsToTs = true } = {}) => { if (jsRE.test(file) && !excludeRE.test(file)) { const tsFile = file.replace(jsRE, '.ts') if (!files[tsFile]) { - let content = files[file] - if (tsLint) { - content = convertLintFlags(content) - } + const content = files[file] files[tsFile] = content } delete files[file] @@ -22,10 +22,7 @@ module.exports = (api, { tsLint = false, convertJsToTs = true } = {}) => { } else { // rename only main file to main.ts const tsFile = api.entryFile.replace(jsRE, '.ts') - let content = files[api.entryFile] - if (tsLint) { - content = convertLintFlags(content) - } + const content = files[api.entryFile] files[tsFile] = content delete files[api.entryFile] } diff --git a/packages/@vue/cli-plugin-typescript/generator/index.js b/packages/@vue/cli-plugin-typescript/generator/index.js index 6d3eb045da..2c86584e18 100644 --- a/packages/@vue/cli-plugin-typescript/generator/index.js +++ b/packages/@vue/cli-plugin-typescript/generator/index.js @@ -1,63 +1,34 @@ -module.exports = (api, { - classComponent, - tsLint, - lintOn = [], - convertJsToTs, - allowJs -}, _, invoking) => { - if (typeof lintOn === 'string') { - lintOn = lintOn.split(',') - } +const pluginDevDeps = require('../package.json').devDependencies + +module.exports = ( + api, + { classComponent, skipLibCheck = true, convertJsToTs, allowJs }, + rootOptions, + invoking +) => { + const isVue3 = rootOptions && rootOptions.vueVersion === '3' api.extendPackage({ devDependencies: { - typescript: '^3.4.5' + typescript: pluginDevDeps.typescript } }) if (classComponent) { - api.extendPackage({ - dependencies: { - 'vue-class-component': '^7.0.2', - 'vue-property-decorator': '^8.1.0' - } - }) - } - - if (tsLint) { - api.extendPackage({ - scripts: { - lint: 'vue-cli-service lint' - } - }) - - if (!lintOn.includes('save')) { + if (isVue3) { api.extendPackage({ - vue: { - lintOnSave: false + dependencies: { + 'vue-class-component': '^8.0.0-0' } }) - } - - if (lintOn.includes('commit')) { + } else { api.extendPackage({ - devDependencies: { - 'lint-staged': '^8.1.5' - }, - gitHooks: { - 'pre-commit': 'lint-staged' - }, - 'lint-staged': { - '*.ts': ['vue-cli-service lint', 'git add'], - '*.vue': ['vue-cli-service lint', 'git add'] + dependencies: { + 'vue-class-component': pluginDevDeps['vue-class-component'], + 'vue-property-decorator': pluginDevDeps['vue-property-decorator'] } }) } - - // lint and fix files on creation complete - api.onCreateComplete(() => { - return require('../lib/tslint')({}, api, true) - }) } // late invoke compat @@ -76,13 +47,29 @@ module.exports = (api, { // eslint-disable-next-line node/no-extraneous-require require('@vue/cli-plugin-eslint/generator').applyTS(api) } + + if (api.hasPlugin('e2e-webdriverio')) { + // eslint-disable-next-line node/no-extraneous-require + require('@vue/cli-plugin-e2e-webdriverio/generator').applyTS(api) + } } api.render('./template', { - isTest: process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG, + skipLibCheck, hasMocha: api.hasPlugin('unit-mocha'), - hasJest: api.hasPlugin('unit-jest') + hasJest: api.hasPlugin('unit-jest'), + hasWebDriverIO: api.hasPlugin('e2e-webdriverio') }) - require('./convert')(api, { tsLint, convertJsToTs }) + if (isVue3) { + api.render('./template-vue3') + + // In Vue 3, TSX interface is defined in https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/types/jsx.d.ts + // So no need to manually add a shim. + api.render((files) => delete files['src/shims-tsx.d.ts']) + } + + require('./convert')(api, { convertJsToTs }) } + +module.exports.after = '@vue/cli-plugin-router' diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue new file mode 100644 index 0000000000..e172da6487 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue @@ -0,0 +1,37 @@ +--- +extend: '@vue/cli-service/generator/template/src/App.vue' +when: "rootOptions.plugins && !rootOptions.plugins['@vue/cli-plugin-router']" +replace: + - !!js/regexp /Welcome to Your Vue\.js App/g + - !!js/regexp / +<%# END_REPLACE %> diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue new file mode 100644 index 0000000000..49f4011c10 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue @@ -0,0 +1,27 @@ +--- +extend: '@vue/cli-service/generator/template/src/components/HelloWorld.vue' +replace: !!js/regexp / diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts new file mode 100644 index 0000000000..3804a43e2f --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/HomeView.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/HomeView.vue new file mode 100644 index 0000000000..7af751a805 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/HomeView.vue @@ -0,0 +1,37 @@ +--- +extend: '@vue/cli-plugin-router/generator/template/src/views/HomeView.vue' +when: "rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-router']" +replace: + - !!js/regexp /Welcome to Your Vue\.js App/ + - !!js/regexp / +<%# END_REPLACE %> diff --git a/packages/@vue/cli-plugin-typescript/generator/template/src/App.vue b/packages/@vue/cli-plugin-typescript/generator/template/src/App.vue index 7fd43b168b..958c756b21 100644 --- a/packages/@vue/cli-plugin-typescript/generator/template/src/App.vue +++ b/packages/@vue/cli-plugin-typescript/generator/template/src/App.vue @@ -1,5 +1,6 @@ --- extend: '@vue/cli-service/generator/template/src/App.vue' +when: "rootOptions.plugins && !rootOptions.plugins['@vue/cli-plugin-router']" replace: - !!js/regexp /Welcome to Your Vue\.js App/g - !!js/regexp / <%# END_REPLACE %> diff --git a/packages/@vue/cli-plugin-typescript/generator/template/tsconfig.json b/packages/@vue/cli-plugin-typescript/generator/template/tsconfig.json index 827e958b2a..bc3cb7946b 100644 --- a/packages/@vue/cli-plugin-typescript/generator/template/tsconfig.json +++ b/packages/@vue/cli-plugin-typescript/generator/template/tsconfig.json @@ -4,7 +4,9 @@ "module": "esnext", "strict": true, "jsx": "preserve", + <%_ if (!options.useTsWithBabel) { _%> "importHelpers": true, + <%_ } _%> "moduleResolution": "node", <%_ if (options.classComponent) { _%> "experimentalDecorators": true, @@ -12,12 +14,23 @@ <%_ if (options.allowJs) { _%> "allowJs": true, <%_ } _%> + <%_ if (skipLibCheck) { _%> + "skipLibCheck": true, + <%_ } _%> "esModuleInterop": true, "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "useDefineForClassFields": true, "sourceMap": true, "baseUrl": ".", "types": [ - "webpack-env"<% if (hasMocha || hasJest) { %>,<% } %> + "webpack-env"<% if (hasMocha || hasJest || hasWebDriverIO) { %>,<% } %> + <%_ if (hasWebDriverIO) { _%> + <% if (!hasMocha && !hasJest) { %>"mocha",<% } %> + "@wdio/mocha-framework", + "expect-webdriverio", + "webdriverio/sync"<% if (hasMocha || hasJest) { %>,<% } %> + <%_ } _%> <%_ if (hasMocha) { _%> "mocha", "chai" diff --git a/packages/@vue/cli-plugin-typescript/generator/template/tslint.json b/packages/@vue/cli-plugin-typescript/generator/template/tslint.json deleted file mode 100644 index 3ea36f9746..0000000000 --- a/packages/@vue/cli-plugin-typescript/generator/template/tslint.json +++ /dev/null @@ -1,21 +0,0 @@ -<%_ if (options.tsLint) { _%> -{ - "defaultSeverity": "warning", - "extends": [ - "tslint:recommended" - ], - "linterOptions": { - "exclude": [ - "node_modules/**" - ] - }, - "rules": { - "indent": [true, "spaces", 2], - "interface-name": false, - "no-consecutive-blank-lines": false, - "object-literal-sort-keys": false, - "ordered-imports": false, - "quotemark": [true, "single"] - } -} -<%_ } _%> diff --git a/packages/@vue/cli-plugin-typescript/index.js b/packages/@vue/cli-plugin-typescript/index.js index e01d681cda..dd9d1224cb 100644 --- a/packages/@vue/cli-plugin-typescript/index.js +++ b/packages/@vue/cli-plugin-typescript/index.js @@ -1,7 +1,6 @@ const path = require('path') module.exports = (api, projectOptions) => { - const fs = require('fs') const useThreads = process.env.NODE_ENV === 'production' && !!projectOptions.parallel api.chainWebpack(config => { @@ -22,23 +21,29 @@ module.exports = (api, projectOptions) => { const tsxRule = config.module.rule('tsx').test(/\.tsx$/) // add a loader to both *.ts & vue - const addLoader = ({ loader, options }) => { - tsRule.use(loader).loader(loader).options(options) - tsxRule.use(loader).loader(loader).options(options) + const addLoader = ({ name, loader, options }) => { + tsRule.use(name).loader(loader).options(options) + tsxRule.use(name).loader(loader).options(options) } - addLoader({ - loader: 'cache-loader', - options: api.genCacheConfig('ts-loader', { - 'ts-loader': require('ts-loader/package.json').version, - 'typescript': require('typescript/package.json').version, - modern: !!process.env.VUE_CLI_MODERN_BUILD - }, 'tsconfig.json') - }) + try { + const cacheLoaderPath = require.resolve('cache-loader') + + addLoader({ + name: 'cache-loader', + loader: cacheLoaderPath, + options: api.genCacheConfig('ts-loader', { + 'ts-loader': require('ts-loader/package.json').version, + 'typescript': require('typescript/package.json').version, + modern: !!process.env.VUE_CLI_MODERN_BUILD + }, 'tsconfig.json') + }) + } catch (e) {} if (useThreads) { addLoader({ - loader: 'thread-loader', + name: 'thread-loader', + loader: require.resolve('thread-loader'), options: typeof projectOptions.parallel === 'number' ? { workers: projectOptions.parallel } @@ -48,11 +53,13 @@ module.exports = (api, projectOptions) => { if (api.hasPlugin('babel')) { addLoader({ - loader: 'babel-loader' + name: 'babel-loader', + loader: require.resolve('babel-loader') }) } addLoader({ - loader: 'ts-loader', + name: 'ts-loader', + loader: require.resolve('ts-loader'), options: { transpileOnly: true, appendTsSuffixTo: ['\\.vue$'], @@ -61,40 +68,42 @@ module.exports = (api, projectOptions) => { } }) // make sure to append TSX suffix - tsxRule.use('ts-loader').loader('ts-loader').tap(options => { + tsxRule.use('ts-loader').loader(require.resolve('ts-loader')).tap(options => { options = Object.assign({}, options) delete options.appendTsSuffixTo options.appendTsxSuffixTo = ['\\.vue$'] return options }) + // this plugin does not play well with jest + cypress setup (tsPluginE2e.spec.js) somehow + // so temporarily disabled for vue-cli tests if (!process.env.VUE_CLI_TEST) { - // this plugin does not play well with jest + cypress setup (tsPluginE2e.spec.js) somehow - // so temporarily disabled for vue-cli tests + let vueCompilerPath + try { + // Vue 2.7+ + vueCompilerPath = require.resolve('vue/compiler-sfc') + } catch (e) { + // Vue 2.6 and lower versions + vueCompilerPath = require.resolve('vue-template-compiler') + } + config .plugin('fork-ts-checker') - .use(require('fork-ts-checker-webpack-plugin'), [{ - vue: true, - tslint: projectOptions.lintOnSave !== false && fs.existsSync(api.resolve('tslint.json')), - formatter: 'codeframe', - // https://github.com/TypeStrong/ts-loader#happypackmode-boolean-defaultfalse - checkSyntacticErrors: useThreads - }]) + .use(require('fork-ts-checker-webpack-plugin'), [{ + typescript: { + extensions: { + vue: { + enabled: true, + compiler: vueCompilerPath + } + }, + diagnosticOptions: { + semantic: true, + // https://github.com/TypeStrong/ts-loader#happypackmode + syntactic: useThreads + } + } + }]) } }) - - if (!api.hasPlugin('eslint')) { - api.registerCommand('lint', { - description: 'lint source files with TSLint', - usage: 'vue-cli-service lint [options] [...files]', - options: { - '--format [formatter]': 'specify formatter (default: codeFrame)', - '--no-fix': 'do not fix errors', - '--formatters-dir [dir]': 'formatter directory', - '--rules-dir [dir]': 'rules directory' - } - }, args => { - return require('./lib/tslint')(args, api) - }) - } } diff --git a/packages/@vue/cli-plugin-typescript/lib/convertLintFlags.js b/packages/@vue/cli-plugin-typescript/lib/convertLintFlags.js deleted file mode 100644 index 5dbf9f96d7..0000000000 --- a/packages/@vue/cli-plugin-typescript/lib/convertLintFlags.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function convertLintFlags (file) { - return file - .replace(/\/\*\s?eslint-(enable|disable)([^*]+)?\*\//g, (_, $1, $2) => { - if ($2) $2 = $2.trim() - return `/* tslint:${$1}${$2 ? `:${$2}` : ``} */` - }) - .replace(/\/\/\s?eslint-disable-(next-)?line(.+)?/g, (_, $1, $2) => { - if ($2) $2 = $2.trim() - return `// tslint:disable-${$1 || ''}line${$2 ? `:${$2}` : ``}` - }) -} diff --git a/packages/@vue/cli-plugin-typescript/lib/tslint.js b/packages/@vue/cli-plugin-typescript/lib/tslint.js deleted file mode 100644 index 5a514b63f9..0000000000 --- a/packages/@vue/cli-plugin-typescript/lib/tslint.js +++ /dev/null @@ -1,149 +0,0 @@ -const fs = require('fs') -const path = require('path') -const globby = require('globby') -const tslint = require('tslint') -const ts = require('typescript') -/* eslint-disable-next-line node/no-extraneous-require */ -const vueCompiler = require('vue-template-compiler') - -const isVueFile = file => /\.vue(\.ts)?$/.test(file) - -// hack to make tslint --fix work for *.vue files: -// we save the non-script parts to a cache right before -// linting the file, and patch fs.writeFileSync to combine the fixed script -// back with the non-script parts. -// this works because (luckily) tslint lints synchronously. -const vueFileCache = new Map() -const writeFileSync = fs.writeFileSync - -const patchWriteFile = () => { - fs.writeFileSync = (file, content, options) => { - if (isVueFile(file)) { - const parts = vueFileCache.get(path.normalize(file)) - if (parts) { - parts.content = content - const { before, after } = parts - content = `${before}\n${content.trim()}\n${after}` - } - } - return writeFileSync(file, content, options) - } -} - -const restoreWriteFile = () => { - fs.writeFileSync = writeFileSync -} - -const parseTSFromVueFile = file => { - // If the file has already been cached, don't read the file again. Use the cache instead. - if (vueFileCache.has(file)) { - return vueFileCache.get(file) - } - - const content = fs.readFileSync(file, 'utf-8') - const { script } = vueCompiler.parseComponent(content, { pad: 'line' }) - if (script && /^tsx?$/.test(script.lang)) { - vueFileCache.set(file, { - before: content.slice(0, script.start), - after: content.slice(script.end), - content: script.content - }) - return script - } -} - -// patch getSourceFile for *.vue files -// so that it returns the - - diff --git a/packages/@vue/cli-service-global/__tests__/globalService.spec.js b/packages/@vue/cli-service-global/__tests__/globalService.spec.js deleted file mode 100644 index 496b243386..0000000000 --- a/packages/@vue/cli-service-global/__tests__/globalService.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -jest.setTimeout(80000) - -const fs = require('fs-extra') -const path = require('path') -const portfinder = require('portfinder') -const createServer = require('@vue/cli-test-utils/createServer') -const execa = require('execa') -const serve = require('@vue/cli-test-utils/serveWithPuppeteer') -const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer') - -const cwd = path.resolve(__dirname, 'temp') -const binPath = require.resolve('@vue/cli/bin/vue') -const write = (file, content) => fs.writeFile(path.join(cwd, file), content) - -const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8') - -const entryJs = ` -import Vue from 'vue' -import App from './Other.vue' - -new Vue({ render: h => h(App) }).$mount('#app') -`.trim() - -beforeEach(async () => { - await fs.ensureDir(cwd) - await write('App.vue', entryVue) - await write('Other.vue', entryVue) - await write('foo.js', entryJs) -}) - -test('global serve', async () => { - await serve( - () => execa(binPath, ['serve'], { cwd }), - async ({ page, nextUpdate, helpers }) => { - expect(await helpers.getText('h1')).toMatch('hi') - write('App.vue', entryVue.replace(`{{ msg }}`, 'Updated')) - await nextUpdate() // wait for child stdout update signal - try { - await page.waitForXPath('//h1[contains(text(), "Updated")]', { timeout: 60000 }) - } catch (e) { - if (process.env.APPVEYOR && e.message.match('timeout')) { - // AppVeyor VM is so slow that there's a large chance this test cases will time out, - // we have to tolerate such failures. - console.error(e) - } else { - throw e - } - } - } - ) -}) - -let server, browser, page -test('global build', async () => { - const { stdout } = await execa(binPath, ['build', 'foo.js'], { cwd }) - - expect(stdout).toMatch('Build complete.') - - const distDir = path.join(cwd, 'dist') - const hasFile = file => fs.existsSync(path.join(distDir, file)) - expect(hasFile('index.html')).toBe(true) - expect(hasFile('js')).toBe(true) - expect(hasFile('css')).toBe(true) - - const port = await portfinder.getPortPromise() - server = createServer({ root: distDir }) - - await new Promise((resolve, reject) => { - server.listen(port, err => { - if (err) return reject(err) - resolve() - }) - }) - - const launched = await launchPuppeteer(`http://localhost:${port}/`) - browser = launched.browser - page = launched.page - - const h1Text = await page.evaluate(() => { - return document.querySelector('h1').textContent - }) - - expect(h1Text).toMatch('hi') -}) - -test('warn if run plain `vue build` or `vue serve` alongside a `package.json` file', async () => { - await write('package.json', `{ - "name": "hello-world", - "version": "1.0.0", - "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build" - } - }`) - - // Warn if a package.json with corresponding `script` field exists - const { stdout } = await execa(binPath, ['build'], { cwd }) - expect(stdout).toMatch(/Did you mean .*(yarn|npm run) build/) - - await fs.unlink(path.join(cwd, 'App.vue')) - - // Fail if no entry file exists, also show a hint for npm scripts - expect(() => { - execa.sync(binPath, ['build'], { cwd }) - }).toThrow(/Did you mean .*(yarn|npm run) build/) - - expect(() => { - execa.sync(binPath, ['serve'], { cwd }) - }).toThrow(/Did you mean .*(yarn|npm run) serve/) - - // clean up, otherwise this file will affect other tests - await fs.unlink(path.join(cwd, 'package.json')) -}) - -afterAll(async () => { - if (browser) { - await browser.close() - } - if (server) { - server.close() - } -}) diff --git a/packages/@vue/cli-service-global/__tests__/globalServiceBuildLib.spec.js b/packages/@vue/cli-service-global/__tests__/globalServiceBuildLib.spec.js deleted file mode 100644 index cfcc876652..0000000000 --- a/packages/@vue/cli-service-global/__tests__/globalServiceBuildLib.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -jest.setTimeout(20000) - -const fs = require('fs-extra') -const path = require('path') -const portfinder = require('portfinder') -const createServer = require('@vue/cli-test-utils/createServer') -const execa = require('execa') -const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer') - -const cwd = path.resolve(__dirname, 'temp') -const binPath = require.resolve('@vue/cli/bin/vue') -const write = (file, content) => fs.writeFile(path.join(cwd, file), content) - -const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8') - -beforeAll(async () => { - await fs.ensureDir(cwd) - await write('testLib.vue', entryVue) -}) - -let server, browser, page -test('global build --target lib', async () => { - const { stdout } = await execa(binPath, ['build', 'testLib.vue', '--target', 'lib'], { cwd }) - - expect(stdout).toMatch('Build complete.') - - const distDir = path.join(cwd, 'dist') - const hasFile = file => fs.existsSync(path.join(distDir, file)) - expect(hasFile('demo.html')).toBe(true) - expect(hasFile('testLib.common.js')).toBe(true) - expect(hasFile('testLib.umd.js')).toBe(true) - expect(hasFile('testLib.umd.min.js')).toBe(true) - expect(hasFile('testLib.css')).toBe(true) - - const port = await portfinder.getPortPromise() - server = createServer({ root: distDir }) - - await new Promise((resolve, reject) => { - server.listen(port, err => { - if (err) return reject(err) - resolve() - }) - }) - - const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`) - browser = launched.browser - page = launched.page - - const h1Text = await page.evaluate(() => { - return document.querySelector('h1').textContent - }) - - expect(h1Text).toMatch('hi') -}) - -afterAll(async () => { - if (browser) { - await browser.close() - } - if (server) { - server.close() - } -}) diff --git a/packages/@vue/cli-service-global/__tests__/globalServiceBuildWc.spec.js b/packages/@vue/cli-service-global/__tests__/globalServiceBuildWc.spec.js deleted file mode 100644 index db9f79d579..0000000000 --- a/packages/@vue/cli-service-global/__tests__/globalServiceBuildWc.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -jest.setTimeout(20000) - -const fs = require('fs-extra') -const path = require('path') -const portfinder = require('portfinder') -const createServer = require('@vue/cli-test-utils/createServer') -const execa = require('execa') -const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer') - -const cwd = path.resolve(__dirname, 'temp') -const binPath = require.resolve('@vue/cli/bin/vue') -const write = (file, content) => fs.writeFile(path.join(cwd, file), content) - -const entryVue = fs.readFileSync(path.resolve(__dirname, 'entry.vue'), 'utf-8') - -beforeAll(async () => { - await fs.ensureDir(cwd) - await write('my-wc.vue', entryVue) -}) - -let server, browser, page -test('global build --target wc', async () => { - const { stdout } = await execa(binPath, ['build', 'my-wc.vue', '--target', 'wc'], { cwd }) - - expect(stdout).toMatch('Build complete.') - - const distDir = path.join(cwd, 'dist') - const hasFile = file => fs.existsSync(path.join(distDir, file)) - expect(hasFile('demo.html')).toBe(true) - expect(hasFile('my-wc.js')).toBe(true) - expect(hasFile('my-wc.min.js')).toBe(true) - - const port = await portfinder.getPortPromise() - server = createServer({ root: distDir }) - - await new Promise((resolve, reject) => { - server.listen(port, err => { - if (err) return reject(err) - resolve() - }) - }) - - const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`) - browser = launched.browser - page = launched.page - - const h1Text = await page.evaluate(() => { - return document.querySelector('my-wc').shadowRoot.querySelector('h1').textContent - }) - - expect(h1Text).toMatch('hi') -}) - -afterAll(async () => { - if (browser) { - await browser.close() - } - if (server) { - server.close() - } -}) diff --git a/packages/@vue/cli-service-global/index.js b/packages/@vue/cli-service-global/index.js deleted file mode 100644 index 9caa1eced7..0000000000 --- a/packages/@vue/cli-service-global/index.js +++ /dev/null @@ -1,95 +0,0 @@ -const fs = require('fs') -const path = require('path') -const chalk = require('chalk') -const Service = require('@vue/cli-service') -const { toPlugin, findExisting } = require('./lib/util') - -const babelPlugin = toPlugin('@vue/cli-plugin-babel') -const eslintPlugin = toPlugin('@vue/cli-plugin-eslint') -const globalConfigPlugin = require('./lib/globalConfigPlugin') - -const context = process.cwd() - -function warnAboutNpmScript (cmd) { - const packageJsonPath = path.join(context, 'package.json') - - if (!fs.existsSync(packageJsonPath)) { - return - } - - let pkg - try { - pkg = require(packageJsonPath) - } catch (e) { - return - } - - if (!pkg.scripts || !pkg.scripts[cmd]) { - return - } - - let script = `npm run ${cmd}` - if (fs.existsSync(path.join(context, 'yarn.lock'))) { - script = `yarn ${cmd}` - } - - console.log(`There's a ${chalk.yellow('package.json')} in the current directory.`) - console.log(`Did you mean ${chalk.yellow(script)}?`) -} - -function resolveEntry (entry, cmd) { - entry = entry || findExisting(context, [ - 'main.js', - 'index.js', - 'App.vue', - 'app.vue' - ]) - - if (!entry) { - console.log(chalk.red(`Failed to locate entry file in ${chalk.yellow(context)}.`)) - console.log(chalk.red(`Valid entry file should be one of: main.js, index.js, App.vue or app.vue.`)) - - console.log() - warnAboutNpmScript(cmd) - process.exit(1) - } - - if (!fs.existsSync(path.join(context, entry))) { - console.log(chalk.red(`Entry file ${chalk.yellow(entry)} does not exist.`)) - - console.log() - warnAboutNpmScript(cmd) - process.exit(1) - } - - warnAboutNpmScript(cmd) - return entry -} - -function createService (entry, asLib) { - return new Service(context, { - projectOptions: { - compiler: true, - lintOnSave: 'default' - }, - plugins: [ - babelPlugin, - eslintPlugin, - globalConfigPlugin(context, entry, asLib) - ] - }) -} - -exports.serve = (_entry, args) => { - const entry = resolveEntry(_entry, 'serve') - createService(entry).run('serve', args) -} - -exports.build = (_entry, args) => { - const entry = resolveEntry(_entry, 'build') - const asLib = args.target && args.target !== 'app' - if (asLib) { - args.entry = entry - } - createService(entry, asLib).run('build', args) -} diff --git a/packages/@vue/cli-service-global/lib/globalConfigPlugin.js b/packages/@vue/cli-service-global/lib/globalConfigPlugin.js deleted file mode 100644 index fa82581ae2..0000000000 --- a/packages/@vue/cli-service-global/lib/globalConfigPlugin.js +++ /dev/null @@ -1,140 +0,0 @@ -const path = require('path') -const resolve = require('resolve') -const { findExisting } = require('./util') - -module.exports = function createConfigPlugin (context, entry, asLib) { - return { - id: '@vue/cli-service-global-config', - apply: (api, options) => { - api.chainWebpack(config => { - // entry is *.vue file, create alias for built-in js entry - if (/\.vue$/.test(entry)) { - config.resolve - .alias - .set('~entry', path.resolve(context, entry)) - entry = require.resolve('../template/main.js') - } else { - // make sure entry is relative - if (!/^\.\//.test(entry)) { - entry = `./${entry}` - } - } - - // ensure core-js polyfills can be imported - config.resolve - .alias - .set('core-js', path.dirname(require.resolve('core-js'))) - .set('regenerator-runtime', path.dirname(require.resolve('regenerator-runtime'))) - - // ensure loaders can be resolved properly - // this is done by locating vue's install location (which is a - // dependency of the global service) - const modulePath = path.resolve(require.resolve('vue'), '../../../') - config.resolveLoader - .modules - .add(modulePath) - - // add resolve alias for vue and vue-hot-reload-api - // but prioritize versions installed locally. - try { - resolve.sync('vue', { basedir: context }) - } catch (e) { - const vuePath = path.dirname(require.resolve('vue')) - config.resolve.alias - .set('vue$', `${vuePath}/${options.compiler ? `vue.esm.js` : `vue.runtime.esm.js`}`) - } - - try { - resolve.sync('vue-hot-reload-api', { basedir: context }) - } catch (e) { - config.resolve.alias - // eslint-disable-next-line node/no-extraneous-require - .set('vue-hot-reload-api', require.resolve('vue-hot-reload-api')) - } - - // set entry - config - .entry('app') - .clear() - .add(entry) - - const babelOptions = { - presets: [require.resolve('@vue/babel-preset-app')] - } - - // set inline babel options - config.module - .rule('js') - .include - .clear() - .end() - .exclude - .add(/node_modules/) - .add(/@vue\/cli-service/) - .end() - .uses - .delete('cache-loader') - .end() - .use('babel-loader') - .tap(() => babelOptions) - - // check eslint config presence - // otherwise eslint-loader goes all the way up to look for eslintrc, can be - // messed up when the project is inside another project. - const ESLintConfigFile = findExisting(context, [ - '.eslintrc.js', - '.eslintrc.yaml', - '.eslintrc.yml', - '.eslintrc.json', - '.eslintrc', - 'package.json' - ]) - const hasESLintConfig = ESLintConfigFile === 'package.json' - ? !!(require(path.join(context, 'package.json')).eslintConfig) - : !!ESLintConfigFile - - // set inline eslint options - config.module - .rule('eslint') - .include - .clear() - .end() - .exclude - .add(/node_modules/) - .end() - .use('eslint-loader') - .tap(loaderOptions => Object.assign({}, loaderOptions, { - useEslintrc: hasESLintConfig, - baseConfig: { - extends: [ - 'plugin:vue/essential', - 'eslint:recommended' - ], - parserOptions: { - parser: 'babel-eslint' - } - } - })) - - if (!asLib) { - // set html plugin template - const indexFile = findExisting(context, [ - 'index.html', - 'public/index.html' - ]) || path.resolve(__dirname, '../template/index.html') - config - .plugin('html') - .tap(args => { - args[0].template = indexFile - return args - }) - } - - // disable copy plugin if no public dir - if (asLib || !findExisting(context, ['public'])) { - config.plugins.delete('copy') - } - }) - } - } -} diff --git a/packages/@vue/cli-service-global/lib/util.js b/packages/@vue/cli-service-global/lib/util.js deleted file mode 100644 index a19dd84343..0000000000 --- a/packages/@vue/cli-service-global/lib/util.js +++ /dev/null @@ -1,34 +0,0 @@ -const fs = require('fs') -const path = require('path') - -exports.toPlugin = id => ({ id, apply: require(id) }) - -// Based on https://stackoverflow.com/questions/27367261/check-if-file-exists-case-sensitive -// Case checking is required, to avoid errors raised by case-sensitive-paths-webpack-plugin -function fileExistsWithCaseSync (filepath) { - const { base, dir, root } = path.parse(filepath) - - if (dir === root || dir === '.') { - return true - } - - try { - const filenames = fs.readdirSync(dir) - if (!filenames.includes(base)) { - return false - } - } catch (e) { - // dir does not exist - return false - } - - return fileExistsWithCaseSync(dir) -} - -exports.findExisting = (context, files) => { - for (const file of files) { - if (fileExistsWithCaseSync(path.join(context, file))) { - return file - } - } -} diff --git a/packages/@vue/cli-service-global/package.json b/packages/@vue/cli-service-global/package.json deleted file mode 100644 index 0ac2966ae7..0000000000 --- a/packages/@vue/cli-service-global/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@vue/cli-service-global", - "version": "4.0.0-rc.4", - "description": "vue-cli-service global addon for vue-cli", - "main": "index.js", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/vuejs/vue-cli.git", - "directory": "packages/@vue/cli-service-global" - }, - "keywords": [ - "vue", - "cli" - ], - "author": "Evan You", - "license": "MIT", - "bugs": { - "url": "https://github.com/vuejs/vue-cli/issues" - }, - "homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-build#readme", - "dependencies": { - "@vue/babel-preset-app": "^4.0.0-rc.4", - "@vue/cli-plugin-babel": "^4.0.0-rc.4", - "@vue/cli-plugin-eslint": "^4.0.0-rc.4", - "@vue/cli-service": "^4.0.0-rc.4", - "babel-eslint": "^10.0.1", - "chalk": "^2.4.2", - "core-js": "^3.1.2", - "eslint": "^5.16.0", - "eslint-plugin-vue": "^5.2.2", - "regenerator-runtime": "^0.13.2", - "resolve": "^1.11.0", - "vue": "^2.6.10", - "vue-template-compiler": "^2.6.10" - } -} diff --git a/packages/@vue/cli-service-global/template/index.html b/packages/@vue/cli-service-global/template/index.html deleted file mode 100644 index be3c262d87..0000000000 --- a/packages/@vue/cli-service-global/template/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Vue CLI App - - -
- - diff --git a/packages/@vue/cli-service-global/template/main.js b/packages/@vue/cli-service-global/template/main.js deleted file mode 100644 index 0076570eb0..0000000000 --- a/packages/@vue/cli-service-global/template/main.js +++ /dev/null @@ -1,6 +0,0 @@ -import Vue from 'vue' -import App from '~entry' - -Vue.config.productionTip = false - -new Vue({ render: h => h(App) }).$mount('#app') diff --git a/packages/@vue/cli-service/__tests__/Service.spec.js b/packages/@vue/cli-service/__tests__/Service.spec.js index 9968cd03c9..366b9425df 100644 --- a/packages/@vue/cli-service/__tests__/Service.spec.js +++ b/packages/@vue/cli-service/__tests__/Service.spec.js @@ -1,22 +1,22 @@ jest.mock('fs') -jest.mock('/vue.config.js', () => ({ lintOnSave: false }), { virtual: true }) jest.mock('vue-cli-plugin-foo', () => () => {}, { virtual: true }) const fs = require('fs') const path = require('path') +const { semver } = require('@vue/cli-shared-utils') const Service = require('../lib/Service') const mockPkg = json => { fs.writeFileSync('/package.json', JSON.stringify(json, null, 2)) } -const createMockService = (plugins = [], init = true, mode) => { +const createMockService = async (plugins = [], init = true, mode) => { const service = new Service('/', { plugins, useBuiltIn: false }) if (init) { - service.init(mode) + await service.init(mode) } return service } @@ -30,11 +30,17 @@ beforeEach(() => { delete process.env.BAZ }) -test('env loading', () => { +afterEach(() => { + if (fs.existsSync('/vue.config.js')) { + fs.unlinkSync('/vue.config.js') + } +}) + +test('env loading', async () => { process.env.FOO = 0 fs.writeFileSync('/.env.local', `FOO=1\nBAR=2`) fs.writeFileSync('/.env', `BAR=3\nBAZ=4`) - createMockService() + await createMockService() expect(process.env.FOO).toBe('0') expect(process.env.BAR).toBe('2') @@ -44,11 +50,11 @@ test('env loading', () => { fs.unlinkSync('/.env') }) -test('env loading for custom mode', () => { +test('env loading for custom mode', async () => { process.env.VUE_CLI_TEST_TESTING_ENV = true fs.writeFileSync('/.env', 'FOO=1') fs.writeFileSync('/.env.staging', 'FOO=2\nNODE_ENV=production') - createMockService([], true, 'staging') + await createMockService([], true, 'staging') expect(process.env.FOO).toBe('2') expect(process.env.NODE_ENV).toBe('production') @@ -61,8 +67,8 @@ test('env loading for custom mode', () => { test('loading plugins from package.json', () => { mockPkg({ devDependencies: { - 'bar': '^1.0.0', - '@vue/cli-plugin-babel': '^4.0.0-rc.4', + bar: '^1.0.0', + '@vue/cli-plugin-babel': '^5.0.0', 'vue-cli-plugin-foo': '^1.0.0' } }) @@ -72,97 +78,103 @@ test('loading plugins from package.json', () => { expect(service.plugins.some(({ id }) => id === 'bar')).toBe(false) }) -test('load project options from package.json', () => { +test('load project options from package.json', async () => { mockPkg({ vue: { lintOnSave: 'default' } }) - const service = createMockService() + const service = await createMockService() expect(service.projectOptions.lintOnSave).toBe('default') }) -test('handle option publicPath and outputDir correctly', () => { +test('handle option publicPath and outputDir correctly', async () => { mockPkg({ vue: { publicPath: 'https://foo.com/bar', outputDir: '/public/' } }) - const service = createMockService() + const service = await createMockService() expect(service.projectOptions.publicPath).toBe('https://foo.com/bar/') expect(service.projectOptions.outputDir).toBe('/public') }) -test('normalize publicPath when relative', () => { +test('normalize publicPath when relative', async () => { mockPkg({ vue: { publicPath: './foo/bar' } }) - const service = createMockService() + const service = await createMockService() expect(service.projectOptions.publicPath).toBe('foo/bar/') }) -test('keep publicPath when empty', () => { +test('allow custom protocol in publicPath', async () => { + mockPkg({ + vue: { + publicPath: 'customprotocol://foo/bar' + } + }) + const service = await createMockService() + expect(service.projectOptions.publicPath).toBe('customprotocol://foo/bar/') +}) + +test('keep publicPath when empty', async () => { mockPkg({ vue: { publicPath: '' } }) - const service = createMockService() + const service = await createMockService() expect(service.projectOptions.publicPath).toBe('') }) -test('load project options from vue.config.js', () => { - process.env.VUE_CLI_SERVICE_CONFIG_PATH = `/vue.config.js` - fs.writeFileSync('/vue.config.js', `module.exports = { lintOnSave: false }`) +test('load project options from vue.config.js', async () => { + fs.writeFileSync(path.resolve('/', 'vue.config.js'), '') // only to ensure fs.existsSync returns true + jest.mock(path.resolve('/', 'vue.config.js'), () => ({ lintOnSave: false }), { virtual: true }) mockPkg({ vue: { lintOnSave: 'default' } }) - const service = createMockService() - fs.unlinkSync('/vue.config.js') - delete process.env.VUE_CLI_SERVICE_CONFIG_PATH + const service = await createMockService() // vue.config.js has higher priority expect(service.projectOptions.lintOnSave).toBe(false) }) -test('load project options from vue.config.js', () => { - process.env.VUE_CLI_SERVICE_CONFIG_PATH = `/vue.config.js` - fs.writeFileSync('/vue.config.js', '') // only to ensure fs.existsSync returns true - jest.mock('/vue.config.js', () => function () { return { lintOnSave: false } }, { virtual: true }) +test('load project options from vue.config.js as a function', async () => { + fs.writeFileSync(path.resolve('/', 'vue.config.js'), '') + jest.mock(path.resolve('/', 'vue.config.js'), () => function () { return { lintOnSave: false } }, { virtual: true }) mockPkg({ vue: { lintOnSave: 'default' } }) - const service = createMockService() - fs.unlinkSync('/vue.config.js') - delete process.env.VUE_CLI_SERVICE_CONFIG_PATH + const service = await createMockService() // vue.config.js has higher priority expect(service.projectOptions.lintOnSave).toBe(false) }) -test('api: assertVersion', () => { +test('api: assertVersion', async () => { const plugin = { id: 'test-assertVersion', apply: api => { - expect(() => api.assertVersion(4)).not.toThrow() - expect(() => api.assertVersion('^4.0.0-0')).not.toThrow() + const majorVersionNumber = semver.major(api.version) + expect(() => api.assertVersion(majorVersionNumber)).not.toThrow() + expect(() => api.assertVersion(`^${majorVersionNumber}.0.0-0`)).not.toThrow() // expect(() => api.assertVersion('>= 4')).not.toThrow() expect(() => api.assertVersion(4.1)).toThrow('Expected string or integer value') expect(() => api.assertVersion('^100')).toThrow('Require @vue/cli-service "^100"') } } - createMockService([plugin], true /* init */) + await createMockService([plugin], true /* init */) }) -test('api: registerCommand', () => { +test('api: registerCommand', async () => { let args - const service = createMockService([{ + const service = await createMockService([{ id: 'test', apply: api => { api.registerCommand('foo', _args => { @@ -171,17 +183,17 @@ test('api: registerCommand', () => { } }]) - service.run('foo', { n: 1 }) + await service.run('foo', { n: 1 }) expect(args).toEqual({ _: [], n: 1 }) }) -test('api: --skip-plugins', () => { +test('api: --skip-plugins', async () => { let untouched = true - const service = createMockService([{ + const service = await createMockService([{ id: 'test-command', apply: api => { api.registerCommand('foo', _args => { - return + }) } }, @@ -192,11 +204,67 @@ test('api: --skip-plugins', () => { } }], false) - service.run('foo', { 'skip-plugins': 'test-plugin' }) + await service.run('foo', { 'skip-plugins': 'test-plugin' }) expect(untouched).toEqual(true) }) -test('api: defaultModes', () => { +describe('internal: gather pluginsToSkip and cleanup args', () => { + let resultingArgs, resultingRawArgv + + const testCommand = { + id: 'test-command', + apply: api => { + api.registerCommand('foo', (_args, _rawArgv) => { + resultingArgs = _args + resultingRawArgv = _rawArgv + }) + } + } + const plugin1 = { + id: 'vue-cli-plugin-test-plugin1', + apply: api => { + } + } + + test('Single --skip-plugins', async () => { + const service = await createMockService([ + testCommand, + plugin1 + ], false) + const args = { 'skip-plugins': 'test-plugin1' } + const rawArgv = ['foo', '--skip-plugins', 'test-plugin1'] + await service.run('foo', args, rawArgv) + expect(resultingArgs).toEqual({ '_': [] }) + expect(resultingRawArgv).toEqual([]) + expect(...service.pluginsToSkip).toEqual('vue-cli-plugin-test-plugin1') + }) + + resultingArgs = resultingRawArgv = undefined + test('Multiple --skip-plugins', async () => { + const service = await createMockService([ + testCommand, + plugin1, + { + id: 'vue-cli-plugin-test-plugin2', + apply: api => { + } + }, + { + id: 'vue-cli-plugin-test-plugin3', + apply: api => { + } + } + ], false) + const args = { 'skip-plugins': ['test-plugin1,test-plugin2', 'test-plugin3'] } + const rawArgv = ['foo', '--skip-plugins', 'test-plugin1,test-plugin2', '--skip-plugins', 'test-plugin3'] + await service.run('foo', args, rawArgv) + expect(resultingArgs).toEqual({ '_': [] }) + expect(resultingRawArgv).toEqual([]) + expect([...service.pluginsToSkip].sort()).toEqual(['vue-cli-plugin-test-plugin1', 'vue-cli-plugin-test-plugin2', 'vue-cli-plugin-test-plugin3']) + }) +}) + +test('api: defaultModes', async () => { fs.writeFileSync('/.env.foo', `FOO=5\nBAR=6`) fs.writeFileSync('/.env.foo.local', `FOO=7\nBAZ=8`) @@ -217,7 +285,7 @@ test('api: defaultModes', () => { foo: 'foo' } - createMockService([plugin1], false /* init */).run('foo') + await (await createMockService([plugin1], false /* init */)).run('foo') delete process.env.NODE_ENV delete process.env.BABEL_ENV @@ -234,11 +302,11 @@ test('api: defaultModes', () => { test: 'test' } - createMockService([plugin2], false /* init */).run('test') + await (await createMockService([plugin2], false /* init */)).run('test') }) -test('api: chainWebpack', () => { - const service = createMockService([{ +test('api: chainWebpack', async () => { + const service = await createMockService([{ id: 'test', apply: api => { api.chainWebpack(config => { @@ -251,8 +319,8 @@ test('api: chainWebpack', () => { expect(config.output.path).toBe('test-dist') }) -test('api: configureWebpack', () => { - const service = createMockService([{ +test('api: configureWebpack', async () => { + const service = await createMockService([{ id: 'test', apply: api => { api.configureWebpack(config => { @@ -267,8 +335,8 @@ test('api: configureWebpack', () => { expect(config.output.path).toBe('test-dist-2') }) -test('api: configureWebpack returning object', () => { - const service = createMockService([{ +test('api: configureWebpack returning object', async () => { + const service = await createMockService([{ id: 'test', apply: api => { api.configureWebpack(config => { @@ -285,8 +353,8 @@ test('api: configureWebpack returning object', () => { expect(config.output.path).toBe('test-dist-3') }) -test('api: configureWebpack preserve ruleNames', () => { - const service = createMockService([ +test('api: configureWebpack preserve ruleNames', async () => { + const service = await createMockService([ { id: 'babel', apply: require('@vue/cli-plugin-babel') @@ -307,8 +375,10 @@ test('api: configureWebpack preserve ruleNames', () => { expect(config.module.rules[0].__ruleNames).toEqual(['js']) }) -test('internal: should correctly set VUE_CLI_ENTRY_FILES', () => { - const service = createMockService([{ +test('internal: should correctly set VUE_CLI_ENTRY_FILES', async () => { + delete process.env.VUE_CLI_ENTRY_FILES + + const service = await createMockService([{ id: 'test', apply: api => { api.configureWebpack(config => { @@ -329,9 +399,9 @@ test('internal: should correctly set VUE_CLI_ENTRY_FILES', () => { ) }) -test('api: configureDevServer', () => { +test('api: configureDevServer', async () => { const cb = () => {} - const service = createMockService([{ + const service = await createMockService([{ id: 'test', apply: api => { api.configureDevServer(cb) @@ -340,8 +410,8 @@ test('api: configureDevServer', () => { expect(service.devServerConfigFns).toContain(cb) }) -test('api: resolve', () => { - createMockService([{ +test('api: resolve', async () => { + await createMockService([{ id: 'test', apply: api => { expect(api.resolve('foo.js')).toBe(path.resolve('/', 'foo.js')) @@ -349,8 +419,8 @@ test('api: resolve', () => { }]) }) -test('api: hasPlugin', () => { - createMockService([ +test('api: hasPlugin', async () => { + await createMockService([ { id: 'vue-cli-plugin-foo', apply: api => { @@ -367,3 +437,50 @@ test('api: hasPlugin', () => { } ]) }) + +test('order: service plugins order', async () => { + const applyCallOrder = [] + function apply (id, order) { + order = order || {} + const fn = jest.fn(() => { applyCallOrder.push(id) }) + fn.after = order.after + return fn + } + const service = new Service('/', { + plugins: [ + { + id: 'vue-cli-plugin-foo', + apply: apply('vue-cli-plugin-foo') + }, + { + id: 'vue-cli-plugin-bar', + apply: apply('vue-cli-plugin-bar', { after: 'vue-cli-plugin-baz' }) + }, + { + id: 'vue-cli-plugin-baz', + apply: apply('vue-cli-plugin-baz') + } + ] + }) + expect(service.plugins.map(p => p.id)).toEqual([ + 'built-in:commands/serve', + 'built-in:commands/build', + 'built-in:commands/inspect', + 'built-in:commands/help', + 'built-in:config/base', + 'built-in:config/assets', + 'built-in:config/css', + 'built-in:config/prod', + 'built-in:config/app', + 'vue-cli-plugin-foo', + 'vue-cli-plugin-baz', + 'vue-cli-plugin-bar' + ]) + + await service.init() + expect(applyCallOrder).toEqual([ + 'vue-cli-plugin-foo', + 'vue-cli-plugin-baz', + 'vue-cli-plugin-bar' + ]) +}) diff --git a/packages/@vue/cli-service/__tests__/ServiceESM.spec.js b/packages/@vue/cli-service/__tests__/ServiceESM.spec.js new file mode 100644 index 0000000000..92014eafdb --- /dev/null +++ b/packages/@vue/cli-service/__tests__/ServiceESM.spec.js @@ -0,0 +1,64 @@ +jest.setTimeout(200000) +const path = require('path') +const fs = require('fs-extra') + +const { defaultPreset } = require('@vue/cli/lib/options') +const create = require('@vue/cli-test-utils/createTestProject') +const { loadModule } = require('@vue/cli-shared-utils') + +let project +beforeAll(async () => { + project = await create('service-esm-test', defaultPreset) + const pkg = JSON.parse(await project.read('package.json')) + pkg.type = 'module' + pkg.vue = { lintOnSave: 'default' } + await project.write('package.json', JSON.stringify(pkg, null, 2)) + fs.renameSync(path.resolve(project.dir, 'babel.config.js'), path.resolve(project.dir, 'babel.config.cjs')) +}) + +const createService = async () => { + const Service = loadModule('@vue/cli-service/lib/Service', project.dir) + const service = new Service(project.dir, { + plugins: [], + useBuiltIn: false + }) + await service.init() + return service +} + +test('load project options from package.json', async () => { + const service = await createService() + expect(service.projectOptions.lintOnSave).toBe('default') +}) + +test('load project options from vue.config.cjs', async () => { + const configPath = path.resolve(project.dir, './vue.config.cjs') + fs.writeFileSync(configPath, 'module.exports = { lintOnSave: true }') + const service = await createService() + expect(service.projectOptions.lintOnSave).toBe(true) + await fs.unlinkSync(configPath) +}) + +test('load project options from vue.config.cjs as a function', async () => { + const configPath = path.resolve(project.dir, './vue.config.cjs') + fs.writeFileSync(configPath, 'module.exports = function () { return { lintOnSave: true } }') + const service = await createService() + expect(service.projectOptions.lintOnSave).toBe(true) + await fs.unlinkSync(configPath) +}) + +test('load project options from vue.config.js', async () => { + const configPath = path.resolve(project.dir, './vue.config.js') + fs.writeFileSync(configPath, 'export default { lintOnSave: true }') + const service = await createService() + expect(service.projectOptions.lintOnSave).toBe(true) + await fs.unlinkSync(configPath) +}) + +test('load project options from vue.config.mjs', async () => { + const configPath = path.resolve(project.dir, './vue.config.mjs') + fs.writeFileSync(configPath, 'export default { lintOnSave: true }') + const service = await createService() + expect(service.projectOptions.lintOnSave).toBe(true) + await fs.unlinkSync(configPath) +}) diff --git a/packages/@vue/cli-service/__tests__/build.spec.js b/packages/@vue/cli-service/__tests__/build.spec.js index 070d29a559..ce94962897 100644 --- a/packages/@vue/cli-service/__tests__/build.spec.js +++ b/packages/@vue/cli-service/__tests__/build.spec.js @@ -27,20 +27,24 @@ test('build', async () => { expect(project.has('dist/subfolder/index.html')).toBe(true) const index = await project.read('dist/index.html') + + // should have set the title inferred from the project name + expect(index).toMatch(/e2e-build<\/title>/) + // should split and preload app.js & vendor.js - expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js rel=preload as=script>/) - expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload as=script>/) + // expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js" rel="preload" as="script">/) + // expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js" rel="preload" as="script">/) // should preload css - expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload as=style>/) + // expect(index).toMatch(/<link [^>]+app[^>]+\.css" rel="preload" as="style">/) // should inject scripts - expect(index).toMatch(/<script src=\/js\/chunk-vendors\.\w{8}\.js>/) - expect(index).toMatch(/<script src=\/js\/app\.\w{8}\.js>/) + expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/) + expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/) // should inject css - expect(index).toMatch(/<link href=\/css\/app\.\w{8}\.css rel=stylesheet>/) + expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet">/) // should reference favicon with correct base URL - expect(index).toMatch(/<link rel=icon href=\/favicon.ico>/) + expect(index).toMatch(/<link rel="icon" href="\/favicon.ico">/) const port = await portfinder.getPortPromise() server = createServer({ root: path.join(project.dir, 'dist') }) @@ -63,6 +67,44 @@ test('build', async () => { expect(h1Text).toMatch('Welcome to Your Vue.js App') }) +test('build with --report-json', async () => { + const project = await create('e2e-build-report-json', defaultPreset) + + const { stdout } = await project.run('vue-cli-service build --report-json') + expect(stdout).toMatch('Build complete.') + // should generate report.json + expect(project.has('dist/report.json')).toBe(true) + + const report = JSON.parse(await project.read('dist/report.json')) + // should contain entry points info + expect(report.entrypoints).toHaveProperty('app.chunks') + expect(report.entrypoints).toHaveProperty('app.assets') + + const appChunk = report.chunks.find(chunk => chunk.names.includes('app')) + // Each chunk should contain meta info + expect(appChunk).toHaveProperty('rendered') + expect(appChunk).toHaveProperty('initial') + expect(appChunk).toHaveProperty('entry') + expect(appChunk).toHaveProperty('size') + expect(appChunk).toHaveProperty('names') + expect(appChunk).toHaveProperty('files') + expect(appChunk).toHaveProperty('modules') +}) + +test('build with --dest', async () => { + const project = await create('e2e-build-dest', defaultPreset) + + const { stdout } = await project.run('vue-cli-service build --dest other_dist') + expect(stdout).toMatch('Build complete.') + + expect(project.has('other_dist/index.html')).toBe(true) + expect(project.has('other_dist/favicon.ico')).toBe(true) + expect(project.has('other_dist/js')).toBe(true) + expect(project.has('other_dist/css')).toBe(true) + + expect(project.has('dist')).toBe(false) +}) + afterAll(async () => { if (browser) { await browser.close() diff --git a/packages/@vue/cli-service/__tests__/buildWcAsync.spec.js b/packages/@vue/cli-service/__tests__/buildWcAsync.spec.js index 1ea2c74976..b8235a7573 100644 --- a/packages/@vue/cli-service/__tests__/buildWcAsync.spec.js +++ b/packages/@vue/cli-service/__tests__/buildWcAsync.spec.js @@ -1,5 +1,6 @@ -jest.setTimeout(15000) +jest.setTimeout(30000) +const fs = require('fs-extra') const path = require('path') const portfinder = require('portfinder') const createServer = require('@vue/cli-test-utils/createServer') @@ -19,10 +20,11 @@ test('build as wc in async mode', async () => { expect(project.has('dist/build-wc-async.min.js')).toBe(true) // code-split chunks - expect(project.has('dist/build-wc-async.1.js')).toBe(true) - expect(project.has('dist/build-wc-async.1.min.js')).toBe(true) - expect(project.has('dist/build-wc-async.2.js')).toBe(true) - expect(project.has('dist/build-wc-async.2.min.js')).toBe(true) + const files = await fs.readdir(path.resolve(project.dir, 'dist')) + const asyncOutputs = files.filter(f => f.match(/build-wc-async\.\d+\.js/)) + const minifiedAsycnOutputs = files.filter(f => f.match(/build-wc-async\.\d+\.min\.js/)) + expect(asyncOutputs.length).toBe(2) + expect(minifiedAsycnOutputs.length).toBe(2) const port = await portfinder.getPortPromise() server = createServer({ root: path.join(project.dir, 'dist') }) diff --git a/packages/@vue/cli-service/__tests__/cors.spec.js b/packages/@vue/cli-service/__tests__/cors.spec.js index 411cd2d2bd..cae2f8e252 100644 --- a/packages/@vue/cli-service/__tests__/cors.spec.js +++ b/packages/@vue/cli-service/__tests__/cors.spec.js @@ -30,9 +30,9 @@ test('build', async () => { // expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload as=style crossorigin>/) // should apply crossorigin and add integrity to scripts and css - expect(index).toMatch(/<script src=\/js\/chunk-vendors\.\w{8}\.js crossorigin integrity=sha384-.{64}\s?>/) - expect(index).toMatch(/<script src=\/js\/app\.\w{8}\.js crossorigin integrity=sha384-.{64}\s?>/) - expect(index).toMatch(/<link href=\/css\/app\.\w{8}\.css rel=stylesheet crossorigin integrity=sha384-.{64}\s?>/) + expect(index).toMatch(/<script defer="defer" type="module" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/) + expect(index).toMatch(/<script defer="defer" type="module" src="\/js\/app\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/) + expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet" crossorigin integrity="sha384-.{64}\s?">/) // verify integrity is correct by actually running it const port = await portfinder.getPortPromise() diff --git a/packages/@vue/cli-service/__tests__/css.spec.js b/packages/@vue/cli-service/__tests__/css.spec.js index 5a6f989308..68e7d7b73b 100644 --- a/packages/@vue/cli-service/__tests__/css.spec.js +++ b/packages/@vue/cli-service/__tests__/css.spec.js @@ -1,5 +1,7 @@ const { logs } = require('@vue/cli-shared-utils') const Service = require('../lib/Service') +const { defaultPreset } = require('@vue/cli/lib/options') +const create = require('@vue/cli-test-utils/createTestProject') beforeEach(() => { logs.warn = [] @@ -17,11 +19,11 @@ const LOADERS = { stylus: 'stylus' } -const genConfig = (pkg = {}, env) => { +const genConfig = async (pkg = {}, env) => { const prevEnv = process.env.NODE_ENV if (env) process.env.NODE_ENV = env const service = new Service('/', { pkg }) - service.init() + await service.init() const config = service.resolveWebpackConfig() process.env.NODE_ENV = prevEnv return config @@ -32,7 +34,7 @@ const findRule = (config, lang, index = 3) => { return rule.test.test(`.${lang}`) }) // all CSS rules have 4 oneOf rules: - // 0 - <style lang="module"> in Vue files + // 0 - <style module> in Vue files // 1 - <style> in Vue files // 2 - *.modules.css imports from JS // 3 - *.css imports from JS @@ -44,7 +46,10 @@ const findLoaders = (config, lang, index) => { if (!rule) { throw new Error(`rule not found for ${lang}`) } - return rule.use.map(({ loader }) => loader.replace(/-loader$/, '')) + return rule.use.map(({ loader }) => { + const match = loader.match(/([^\\/]+)-loader/) + return match ? match[1] : loader + }) } const findOptions = (config, lang, _loader, index) => { @@ -53,13 +58,13 @@ const findOptions = (config, lang, _loader, index) => { return use.options || {} } -test('default loaders', () => { - const config = genConfig({ postcss: {}}) +test('default loaders', async () => { + const config = await genConfig() LANGS.forEach(lang => { const loader = lang === 'css' ? [] : LOADERS[lang] expect(findLoaders(config, lang)).toEqual(['vue-style', 'css', 'postcss'].concat(loader)) - expect(findOptions(config, lang, 'postcss').plugins).toBeFalsy() + expect(findOptions(config, lang, 'postcss').postcssOptions.plugins).toEqual([require('autoprefixer')]) // assert css-loader options expect(findOptions(config, lang, 'css')).toEqual({ sourceMap: false, @@ -75,12 +80,12 @@ test('default loaders', () => { }) }) -test('production defaults', () => { - const config = genConfig({ postcss: {}}, 'production') +test('production defaults', async () => { + const config = await genConfig({}, 'production') LANGS.forEach(lang => { const loader = lang === 'css' ? [] : LOADERS[lang] expect(findLoaders(config, lang)).toEqual([extractLoaderPath, 'css', 'postcss'].concat(loader)) - expect(findOptions(config, lang, 'postcss').plugins).toBeFalsy() + expect(findOptions(config, lang, 'postcss').postcssOptions.plugins).toEqual([require('autoprefixer')]) expect(findOptions(config, lang, 'css')).toEqual({ sourceMap: false, importLoaders: 2 @@ -88,32 +93,21 @@ test('production defaults', () => { }) }) -test('CSS Modules rules', () => { - const config = genConfig({ - vue: { - css: { - requireModuleExtension: false - } - } - }) +test('override postcss config', async () => { + const config = await genConfig({ postcss: {} }) LANGS.forEach(lang => { - const expected = { - importLoaders: 1, // no postcss-loader + const loader = lang === 'css' ? [] : LOADERS[lang] + expect(findLoaders(config, lang)).toEqual(['vue-style', 'css', 'postcss'].concat(loader)) + expect(findOptions(config, lang, 'postcss').postcssOptions).toBeFalsy() + // assert css-loader options + expect(findOptions(config, lang, 'css')).toEqual({ sourceMap: false, - modules: { - localIdentName: `[name]_[local]_[hash:base64:5]` - } - } - // vue-modules rules - expect(findOptions(config, lang, 'css', 0)).toEqual(expected) - // normal-modules rules - expect(findOptions(config, lang, 'css', 2)).toEqual(expected) - // normal rules - expect(findOptions(config, lang, 'css', 3)).toEqual(expected) + importLoaders: 2 + }) }) }) -test('Customized CSS Modules rules', () => { +test('Customized CSS Modules rules', async () => { const userOptions = { vue: { css: { @@ -128,57 +122,19 @@ test('Customized CSS Modules rules', () => { } } - expect(() => { - genConfig(userOptions) - }).toThrow('`css.requireModuleExtension` is required when custom css modules options provided') - - userOptions.vue.css.requireModuleExtension = true - const config = genConfig(userOptions) + const config = await genConfig(userOptions) LANGS.forEach(lang => { const expected = { - importLoaders: 1, // no postcss-loader + importLoaders: 2, // with postcss-loader sourceMap: false, modules: { localIdentName: `[folder]-[name]-[local][emoji]` } } // vue-modules rules - expect(findOptions(config, lang, 'css', 0)).toEqual(expected) - // normal-modules rules - expect(findOptions(config, lang, 'css', 2)).toEqual(expected) - // normal rules - expect(findOptions(config, lang, 'css', 3)).not.toEqual(expected) - }) -}) - -test('deprecate `css.modules` option', () => { - const config = genConfig({ - vue: { - css: { - modules: true, - loaderOptions: { - css: { - modules: { - localIdentName: '[folder]-[name]-[local][emoji]' - } - } - } - } - } - }) - expect(logs.warn.some(([msg]) => msg.match('please use "css.requireModuleExtension" instead'))).toBe(true) - - LANGS.forEach(lang => { - const expected = { - importLoaders: 1, // no postcss-loader - sourceMap: false, - modules: { - localIdentName: `[folder]-[name]-[local][emoji]` - } - } - // vue-modules rules - expect(findOptions(config, lang, 'css', 0)).toEqual(expected) + expect(findOptions(config, lang, 'css', 0)).toMatchObject(expected) + expect(findOptions(config, lang, 'css', 0).modules.auto.toString()).toEqual('() => true') // normal-modules rules expect(findOptions(config, lang, 'css', 2)).toEqual(expected) // normal rules @@ -186,45 +142,8 @@ test('deprecate `css.modules` option', () => { }) }) -test('favor `css.requireModuleExtension` over `css.modules`', () => { - const config = genConfig({ - vue: { - css: { - requireModuleExtension: false, - modules: false, - - loaderOptions: { - css: { - modules: { - localIdentName: '[folder]-[name]-[local][emoji]' - } - } - } - } - } - }) - - expect(logs.warn.some(([msg]) => msg.match('"css.modules" will be ignored in favor of "css.requireModuleExtension"'))).toBe(true) - - LANGS.forEach(lang => { - const expected = { - importLoaders: 1, // no postcss-loader - sourceMap: false, - modules: { - localIdentName: `[folder]-[name]-[local][emoji]` - } - } - // vue-modules rules - expect(findOptions(config, lang, 'css', 0)).toEqual(expected) - // normal-modules rules - expect(findOptions(config, lang, 'css', 2)).toEqual(expected) - // normal rules - expect(findOptions(config, lang, 'css', 3)).toEqual(expected) - }) -}) - -test('css.extract', () => { - const config = genConfig({ +test('css.extract', async () => { + const config = await genConfig({ vue: { css: { extract: false @@ -233,14 +152,14 @@ test('css.extract', () => { }, 'production') LANGS.forEach(lang => { const loader = lang === 'css' ? [] : LOADERS[lang] - // when extract is false in production, even without postcss config, - // an instance of postcss-loader is injected for inline minification. - expect(findLoaders(config, lang)).toEqual(['vue-style', 'css', 'postcss'].concat(loader)) - expect(findOptions(config, lang, 'css').importLoaders).toBe(2) - expect(findOptions(config, lang, 'postcss').plugins).toBeTruthy() + // when extract is false in production, + // an additional instance of postcss-loader is injected for inline minification. + expect(findLoaders(config, lang)).toEqual(['vue-style', 'css', 'postcss', 'postcss'].concat(loader)) + expect(findOptions(config, lang, 'css').importLoaders).toBe(3) + expect(findOptions(config, lang, 'postcss').postcssOptions.plugins).toBeTruthy() }) - const config2 = genConfig({ + const config2 = await genConfig({ postcss: {}, vue: { css: { @@ -255,12 +174,12 @@ test('css.extract', () => { expect(findLoaders(config2, lang)).toEqual(['vue-style', 'css', 'postcss', 'postcss'].concat(loader)) expect(findOptions(config2, lang, 'css').importLoaders).toBe(3) // minification loader should be injected before the user-facing postcss-loader - expect(findOptions(config2, lang, 'postcss').plugins).toBeTruthy() + expect(findOptions(config2, lang, 'postcss').postcssOptions.plugins).toBeTruthy() }) }) -test('css.sourceMap', () => { - const config = genConfig({ +test('css.sourceMap', async () => { + const config = await genConfig({ postcss: {}, vue: { css: { @@ -275,9 +194,9 @@ test('css.sourceMap', () => { }) }) -test('css-loader options', () => { +test('css-loader options', async () => { const localIdentName = '[name]__[local]--[hash:base64:5]' - const config = genConfig({ + const config = await genConfig({ vue: { css: { loaderOptions: { @@ -300,14 +219,17 @@ test('css-loader options', () => { }) }) -test('css.loaderOptions', () => { +test('css.loaderOptions', async () => { const prependData = '$env: production;' - const config = genConfig({ + const config = await genConfig({ vue: { css: { loaderOptions: { sass: { - prependData + prependData, + sassOptions: { + includePaths: ['./src/styles'] + } } } } @@ -316,22 +238,27 @@ test('css.loaderOptions', () => { expect(findOptions(config, 'scss', 'sass')).toMatchObject({ prependData, - sourceMap: false + sourceMap: false, + sassOptions: { + includePaths: ['./src/styles'] + } }) + expect(findOptions(config, 'scss', 'sass').sassOptions).not.toHaveProperty('indentedSyntax') expect(findOptions(config, 'sass', 'sass')).toMatchObject({ prependData, sassOptions: { - indentedSyntax: true + indentedSyntax: true, + includePaths: ['./src/styles'] }, sourceMap: false }) }) -test('scss loaderOptions', () => { +test('scss loaderOptions', async () => { const sassData = '$env: production' const scssData = '$env: production;' - const config = genConfig({ + const config = await genConfig({ vue: { css: { loaderOptions: { @@ -339,7 +266,8 @@ test('scss loaderOptions', () => { prependData: sassData }, scss: { - prependData: scssData + prependData: scssData, + webpackImporter: false } } } @@ -357,18 +285,175 @@ test('scss loaderOptions', () => { }, sourceMap: false }) -}) -test('should use dart sass implementation whenever possible', () => { - const config = genConfig() - expect(findOptions(config, 'scss', 'sass')).toMatchObject({ implementation: require('sass') }) - expect(findOptions(config, 'sass', 'sass')).toMatchObject({ implementation: require('sass') }) + // should not merge scss options into default sass config + expect(findOptions(config, 'sass', 'sass')).not.toHaveProperty('webpackImporter') }) -test('skip postcss-loader if no postcss config found', () => { - const config = genConfig() - LANGS.forEach(lang => { - const loader = lang === 'css' ? [] : LOADERS[lang] - expect(findLoaders(config, lang)).toEqual(['vue-style', 'css'].concat(loader)) - }) -}) +test('Auto recognition of CSS Modules by file names', async () => { + const project = await create('css-modules-auto', defaultPreset) + await project.write('vue.config.js', 'module.exports = { filenameHashing: false }\n') + + await project.write('src/App.vue', `<template> + <div id="app" :class="$style.red"> + <img alt="Vue logo" src="./assets/logo.png"> + <HelloWorld msg="Welcome to Your Vue.js App"/> + </div> +</template> + +<script> +import HelloWorld from './components/HelloWorld.vue' +import style1 from './style.module.css' +import style2 from './style.css' + +console.log(style1, style2) + +export default { + name: 'App', + components: { + HelloWorld + } +} +</script> + +<style module> +.red { + color: red; +} +</style> +`) + await project.write('src/style.module.css', `.green { color: green; }\n`) + await project.write('src/style.css', `.yellow { color: yellow; }\n`) + + const { stdout } = await project.run('vue-cli-service build') + + expect(stdout).toMatch('Build complete.') + + const appCss = await project.read('dist/css/app.css') + + // <style module> successfully transformed + expect(appCss).not.toMatch('.red') + expect(appCss).toMatch('color: red') + + // style.module.css successfully transformed + expect(appCss).not.toMatch('.green') + expect(appCss).toMatch('color: green') + + // class names in style.css should not be transformed + expect(appCss).toMatch('.yellow') + expect(appCss).toMatch('color: yellow') + + const appJs = await project.read('dist/js/app.js') + + // should contain the class name map in js + expect(appJs).toMatch(/\{"red":/) + expect(appJs).toMatch(/\{"green":/) + expect(appJs).not.toMatch(/\{"yellow":/) +}, 300000) + +test('CSS Moduels Options', async () => { + const project = await create('css-modules-options', defaultPreset) + + await project.write('src/App.vue', `<template> + <div id="app" :class="$style.red"> + <img alt="Vue logo" src="./assets/logo.png"> + <HelloWorld msg="Welcome to Your Vue.js App"/> + </div> +</template> + +<script> +import HelloWorld from './components/HelloWorld.vue' +import style1 from './style.module.css' +import style2 from './style.css' + +console.log(style1, style2) + +export default { + name: 'App', + components: { + HelloWorld + } +} +</script> + +<style module> +.red { + color: red; +} +</style> +`) + await project.write('src/style.module.css', `.green { color: green; }\n`) + await project.write('src/style.css', `.yellow { color: yellow; }\n`) + + // disable CSS Modules + await project.write( + 'vue.config.js', + `module.exports = { + filenameHashing: false, + css: { + loaderOptions: { + css: { + modules: false + } + } + } + }` + ) + let { stdout } = await project.run('vue-cli-service build') + expect(stdout).toMatch('Build complete.') + let appCss = await project.read('dist/css/app.css') + + // <style module> works anyway + expect(appCss).not.toMatch('.red') + expect(appCss).toMatch('color: red') + // style.module.css should not be transformed + expect(appCss).toMatch('.green') + expect(appCss).toMatch('color: green') + // class names in style.css should not be transformed + expect(appCss).toMatch('.yellow') + expect(appCss).toMatch('color: yellow') + + let appJs = await project.read('dist/js/app.js') + + // should not contain class name map + expect(appJs).toMatch(/\{"red":/) // <style module> works anyway + expect(appJs).not.toMatch(/\{"green":/) + expect(appJs).not.toMatch(/\{"yellow":/) + + // enable CSS Modules for all files + await project.write( + 'vue.config.js', + `module.exports = { + filenameHashing: false, + css: { + loaderOptions: { + css: { + modules: { + auto: () => true + } + } + } + } + }` + ) + + stdout = (await project.run('vue-cli-service build')).stdout + expect(stdout).toMatch('Build complete.') + appCss = await project.read('dist/css/app.css') + + // <style module> works anyway + expect(appCss).not.toMatch('.red') + expect(appCss).toMatch('color: red') + // style.module.css should be transformed + expect(appCss).not.toMatch('.green') + expect(appCss).toMatch('color: green') + // class names in style.css should be transformed + expect(appCss).not.toMatch('.yellow') + expect(appCss).toMatch('color: yellow') + + appJs = await project.read('dist/js/app.js') + // should contain class name map + expect(appJs).toMatch(/\{"red":/) + expect(appJs).toMatch(/\{"green":/) + expect(appJs).toMatch(/\{"yellow":/) +}, 300000) diff --git a/packages/@vue/cli-service/__tests__/cssPreprocessors.spec.js b/packages/@vue/cli-service/__tests__/cssPreprocessors.spec.js new file mode 100644 index 0000000000..df2cacb11a --- /dev/null +++ b/packages/@vue/cli-service/__tests__/cssPreprocessors.spec.js @@ -0,0 +1,87 @@ +jest.setTimeout(300000) + +const create = require('@vue/cli-test-utils/createTestProject') +const { defaultPreset } = require('@vue/cli/lib/options') + +test('autoprefixer', async () => { + const project = await create('css-autoprefixer', defaultPreset) + + await project.write('vue.config.js', 'module.exports = { filenameHashing: false }\n') + + const appVue = await project.read('src/App.vue') + await project.write('src/App.vue', appVue.replace('#app {', '#app {\n user-select: none;')) + + await project.run('vue-cli-service build') + + const css = await project.read('dist/css/app.css') + expect(css).toMatch('-webkit-user-select') +}) + +test('CSS inline minification', async () => { + const project = await create('css-inline-minification', defaultPreset) + + await project.write('vue.config.js', 'module.exports = { filenameHashing: false, css: { extract: false } }\n') + + const appVue = await project.read('src/App.vue') + await project.write('src/App.vue', + appVue.replace( + '#app {', + + '#app {\n height: calc(100px * 2);' + ) + ) + await project.run('vue-cli-service build') + const appJs = await project.read('dist/js/app.js') + expect(appJs).not.toMatch('calc(100px') + expect(appJs).toMatch('height:200px;') +}) + +test('CSS minification', async () => { + const project = await create('css-minification', defaultPreset) + + await project.write('vue.config.js', 'module.exports = { filenameHashing: false }\n') + + const appVue = await project.read('src/App.vue') + await project.write('src/App.vue', + appVue.replace( + '#app {', + + '#app {\n height: calc(100px * 2);' + ) + ) + process.env.VUE_CLI_TEST_MINIMIZE = true + await project.run('vue-cli-service build') + const appCss = await project.read('dist/css/app.css') + expect(appCss).not.toMatch('calc(100px') + expect(appCss).toMatch('height:200px;') +}) + +test('Custom PostCSS plugins', async () => { + const project = await create('css-custom-postcss', defaultPreset) + await project.write('vue.config.js', ` + const toRedPlugin = () => { + return { + postcssPlugin: 'to-red', + Declaration (decl) { + if (decl.prop === 'color') { + decl.value = 'red' + } + } + } + } + toRedPlugin.postcss = true + + module.exports = { + filenameHashing: false, + css: { + loaderOptions: { + postcss: { + postcssOptions: { plugins: [toRedPlugin] } + } + } + } + }`) + await project.run('vue-cli-service build') + const appCss = await project.read('dist/css/app.css') + expect(appCss).toMatch('color:red') +}) diff --git a/packages/@vue/cli-service/__tests__/generator.spec.js b/packages/@vue/cli-service/__tests__/generator.spec.js index 47537fc132..19bfc7f68b 100644 --- a/packages/@vue/cli-service/__tests__/generator.spec.js +++ b/packages/@vue/cli-service/__tests__/generator.spec.js @@ -1,46 +1,41 @@ const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin') -test('sass (default)', async () => { - const { pkg, files } = await generateWithPlugin([ +function generateWithOptions (options) { + return generateWithPlugin([ { id: '@vue/cli-service', apply: require('../generator'), - options: { - cssPreprocessor: 'sass' - } + options } ]) +} + +test('sass (default)', async () => { + const { pkg, files } = await generateWithOptions({ + cssPreprocessor: 'sass' + }) expect(files['src/App.vue']).toMatch('<style lang="scss">') expect(pkg).toHaveProperty(['devDependencies', 'sass']) }) -test('node sass', async () => { - const { pkg, files } = await generateWithPlugin([ - { - id: '@vue/cli-service', - apply: require('../generator'), - options: { - cssPreprocessor: 'node-sass' - } - } - ]) +test('dart sass', async () => { + const { pkg, files } = await generateWithOptions({ + cssPreprocessor: 'dart-sass' + }) expect(files['src/App.vue']).toMatch('<style lang="scss">') - expect(pkg).toHaveProperty(['devDependencies', 'node-sass']) + expect(pkg).toHaveProperty(['devDependencies', 'sass']) }) -test('dart sass', async () => { - const { pkg, files } = await generateWithPlugin([ - { - id: '@vue/cli-service', - apply: require('../generator'), - options: { - cssPreprocessor: 'dart-sass' - } - } - ]) +test('Vue 3', async () => { + const { pkg, files } = await generateWithOptions({ + vueVersion: '3' + }) - expect(files['src/App.vue']).toMatch('<style lang="scss">') - expect(pkg).toHaveProperty(['devDependencies', 'sass']) + expect(pkg.dependencies.vue).toMatch('^3') + + expect(files['src/main.js']).toMatch(`import { createApp } from 'vue'`) + + expect(files['src/App.vue']).not.toMatch('<div id="app">') }) diff --git a/packages/@vue/cli-service/__tests__/modernMode.spec.js b/packages/@vue/cli-service/__tests__/modernMode.spec.js index c6b6d146da..fa795fcf09 100644 --- a/packages/@vue/cli-service/__tests__/modernMode.spec.js +++ b/packages/@vue/cli-service/__tests__/modernMode.spec.js @@ -12,7 +12,7 @@ let server, browser test('modern mode', async () => { const project = await create('modern-mode', defaultPreset) - const { stdout } = await project.run('vue-cli-service build --modern') + const { stdout } = await project.run('vue-cli-service build') expect(stdout).toMatch('Build complete.') // assert correct bundle files @@ -31,33 +31,29 @@ test('modern mode', async () => { // assert correct asset links const index = await project.read('dist/index.html') - // should use <script type="module" crossorigin=use-credentials> for modern bundle - expect(index).toMatch(/<script type=module src=\/js\/chunk-vendors\.\w{8}\.js>/) - expect(index).toMatch(/<script type=module src=\/js\/app\.\w{8}\.js>/) + // should use <script type="module" crossorigin="use-credentials"> for modern bundle + expect(index).toMatch(/<script defer="defer" type="module" src="\/js\/chunk-vendors\.\w{8}\.js">/) + expect(index).toMatch(/<script defer="defer" type="module" src="\/js\/app\.\w{8}\.js">/) - // should use <link rel="modulepreload" crossorigin=use-credentials> for modern bundle - expect(index).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload as=script>/) - expect(index).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload as=script>/) + // should use <link rel="modulepreload" crossorigin="use-credentials"> for modern bundle + // expect(index).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js" rel="modulepreload" as="script">/) + // expect(index).toMatch(/<link [^>]*js\/app\.\w{8}\.js" rel="modulepreload" as="script">/) // should use <script nomodule> for legacy bundle - expect(index).toMatch(/<script src=\/js\/chunk-vendors-legacy\.\w{8}\.js nomodule>/) - expect(index).toMatch(/<script src=\/js\/app-legacy\.\w{8}\.js nomodule>/) - - // should inject Safari 10 nomodule fix - const { safariFix } = require('../lib/webpack/ModernModePlugin') - expect(index).toMatch(`<script>${safariFix}</script>`) + expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/) + expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/) // Test crossorigin="use-credentials" await project.write('vue.config.js', `module.exports = { crossorigin: 'use-credentials' }`) - const { stdout: stdout2 } = await project.run('vue-cli-service build --modern') + const { stdout: stdout2 } = await project.run('vue-cli-service build') expect(stdout2).toMatch('Build complete.') const index2 = await project.read('dist/index.html') - // should use <script type="module" crossorigin=use-credentials> for modern bundle - expect(index2).toMatch(/<script type=module src=\/js\/chunk-vendors\.\w{8}\.js crossorigin=use-credentials>/) - expect(index2).toMatch(/<script type=module src=\/js\/app\.\w{8}\.js crossorigin=use-credentials>/) - // should use <link rel="modulepreload" crossorigin=use-credentials> for modern bundle - expect(index2).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload as=script crossorigin=use-credentials>/) - expect(index2).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload as=script crossorigin=use-credentials>/) + // should use <script type="module" crossorigin="use-credentials"> for modern bundle + expect(index2).toMatch(/<script defer="defer" type="module" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin="use-credentials">/) + expect(index2).toMatch(/<script defer="defer" type="module" src="\/js\/app\.\w{8}\.js" crossorigin="use-credentials">/) + // should use <link rel="modulepreload" crossorigin="use-credentials"> for modern bundle + // expect(index2).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js" rel="modulepreload" as="script" crossorigin="use-credentials">/) + // expect(index2).toMatch(/<link [^>]*js\/app\.\w{8}\.js" rel="modulepreload" as="script" crossorigin="use-credentials">/) // start server and ensure the page loads properly const port = await portfinder.getPortPromise() @@ -82,21 +78,86 @@ test('modern mode', async () => { expect(await getH1Text()).toMatch('Welcome to Your Vue.js App') }) -test('no-unsafe-inline', async () => { - const project = await create('no-unsafe-inline', defaultPreset) +test('should not inject the nomodule-fix script if Safari 10 is not targeted', async () => { + // the default targets already excludes safari 10 + const project = await create('skip-safari-fix', defaultPreset) - const { stdout } = await project.run('vue-cli-service build --modern --no-unsafe-inline') + const { stdout } = await project.run('vue-cli-service build') + expect(stdout).toMatch('Build complete.') + + // should contain no inline scripts in the output html + const index = await project.read('dist/index.html') + expect(index).not.toMatch(/[^>]\s*<\/script>/) + // should not contain the safari-nomodule-fix bundle, either + const files = await fs.readdir(path.join(project.dir, 'dist/js')) + expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(false) +}) + +test('should inject nomodule-fix script when Safari 10 support is required', async () => { + const project = await create('safari-nomodule-fix', defaultPreset) + + const pkg = JSON.parse(await project.read('package.json')) + pkg.browserslist.push('safari > 10') + await project.write('package.json', JSON.stringify(pkg, null, 2)) + + const { stdout } = await project.run('vue-cli-service build') expect(stdout).toMatch('Build complete.') // should output a separate safari-nomodule-fix bundle const files = await fs.readdir(path.join(project.dir, 'dist/js')) expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(true) - - // should contain no inline scripts in the output html const index = await project.read('dist/index.html') + // should contain no inline scripts in the output html expect(index).not.toMatch(/[^>]\s*<\/script>/) }) +test('--no-module', async () => { + const project = await create('no-module', defaultPreset) + + const { stdout } = await project.run('vue-cli-service build --no-module') + expect(stdout).toMatch('Build complete.') + + const index = await project.read('dist/index.html') + expect(index).not.toMatch('type="module"') + + const files = await fs.readdir(path.join(project.dir, 'dist/js')) + expect(files.some(f => /-legacy.js/.test(f))).toBe(false) +}) + +test('should use correct hash for fallback bundles', async () => { + const project = await create('legacy-hash', defaultPreset) + + const { stdout } = await project.run('vue-cli-service build') + expect(stdout).toMatch('Build complete.') + + const index = await project.read('dist/index.html') + const jsFiles = (await fs.readdir(path.join(project.dir, 'dist/js'))).filter(f => f.endsWith('.js')) + for (const f of jsFiles) { + if (f.includes('legacy')) { + expect(index).toMatch(`<script defer="defer" src="/js/${f}"`) + } else { + expect(index).toMatch(`<script defer="defer" type="module" src="/js/${f}"`) + } + } +}) + +test('should only build one bundle if all targets support ES module', async () => { + const project = await create('no-differential-loading', defaultPreset) + + const pkg = JSON.parse(await project.read('package.json')) + pkg.browserslist.push('not ie <= 11') + await project.write('package.json', JSON.stringify(pkg, null, 2)) + + const { stdout } = await project.run('vue-cli-service build') + expect(stdout).toMatch('Build complete.') + + const index = await project.read('dist/index.html') + expect(index).not.toMatch('type="module"') + + const files = await fs.readdir(path.join(project.dir, 'dist/js')) + expect(files.some(f => /-legacy.js/.test(f))).toBe(false) +}) + afterAll(async () => { if (browser) { await browser.close() diff --git a/packages/@vue/cli-service/__tests__/multiPage.spec.js b/packages/@vue/cli-service/__tests__/multiPage.spec.js index c85e2c4f65..43ed1ffd8b 100644 --- a/packages/@vue/cli-service/__tests__/multiPage.spec.js +++ b/packages/@vue/cli-service/__tests__/multiPage.spec.js @@ -70,10 +70,10 @@ test('serve w/ multi page', async () => { async ({ page, url, helpers }) => { expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`) - await page.goto(`${url}/foo.html`) + await page.goto(`${url}foo.html`) expect(await helpers.getText('h1')).toMatch(`Foo`) - await page.goto(`${url}/bar.html`) + await page.goto(`${url}bar.html`) expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`) await page.goto(`${url}foo`) @@ -109,62 +109,62 @@ test('build w/ multi page', async () => { const assertSharedAssets = file => { // should split and preload vendor chunk - expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js rel=preload as=script>/) - expect(file).toMatch(/<script [^>]*src=\/js\/chunk-vendors\.\w+\.js>/) + // expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js" rel="preload" as="script">/) + expect(file).toMatch(/<script [^>]*type="module" src="\/js\/chunk-vendors\.\w+\.js">/) } const index = await project.read('dist/index.html') assertSharedAssets(index) // should split and preload common js and css - expect(index).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js rel=preload as=script>/) - expect(index).toMatch(/<script [^>]*src=\/js\/chunk-common\.\w+\.js>/) - expect(index).toMatch(/<link href=\/css\/chunk-common\.\w+\.css rel=stylesheet>/) - expect(index).toMatch(/<link [^>]*chunk-common[^>]*\.css rel=preload as=style>/) + // expect(index).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/) + expect(index).toMatch(/<script [^>]*type="module" src="\/js\/chunk-common\.\w+\.js">/) + expect(index).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/) + // expect(index).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/) // should preload correct page file - expect(index).toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/) - expect(index).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/) - expect(index).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/) + // expect(index).toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/) + // expect(index).not.toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/) + // expect(index).not.toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/) // should prefetch async chunk js and css - expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/) - expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js rel=prefetch>/) + // expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/) + // expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/) // should load correct page js - expect(index).toMatch(/<script [^>]*src=\/js\/index\.\w+\.js>/) - expect(index).not.toMatch(/<script [^>]*src=\/js\/foo\.\w+\.js>/) - expect(index).not.toMatch(/<script [^>]*src=\/js\/bar\.\w+\.js>/) + expect(index).toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js">/) + expect(index).not.toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js">/) + expect(index).not.toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/) const foo = await project.read('dist/foo.html') assertSharedAssets(foo) // should preload correct page file - expect(foo).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/) - expect(foo).toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/) - expect(foo).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/) + // expect(foo).not.toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/) + // expect(foo).toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/) + // expect(foo).not.toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/) // should not prefetch async chunk js and css because it's not used by // this entry - expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/) - expect(foo).not.toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js rel=prefetch>/) + // expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/) + // expect(foo).not.toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/) // should load correct page js - expect(foo).not.toMatch(/<script [^>]*src=\/js\/index\.\w+\.js>/) - expect(foo).toMatch(/<script [^>]*src=\/js\/foo\.\w+\.js>/) - expect(foo).not.toMatch(/<script [^>]*src=\/js\/bar\.\w+\.js>/) + expect(foo).not.toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js">/) + expect(foo).toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js">/) + expect(foo).not.toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/) const bar = await project.read('dist/bar.html') assertSharedAssets(bar) // bar & index have a shared common chunk (App.vue) - expect(bar).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js rel=preload as=script>/) - expect(bar).toMatch(/<script [^>]*src=\/js\/chunk-common\.\w+\.js>/) - expect(bar).toMatch(/<link href=\/css\/chunk-common\.\w+\.css rel=stylesheet>/) - expect(bar).toMatch(/<link [^>]*chunk-common[^>]*\.css rel=preload as=style>/) + // expect(bar).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/) + // expect(bar).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js">/) + expect(bar).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/) + // expect(bar).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/) // should preload correct page file - expect(bar).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/) - expect(bar).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/) - expect(bar).toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/) + // expect(bar).not.toMatch(/<link [^>]*js\/index[^>]*\.js" rel="preload" as="script">/) + // expect(bar).not.toMatch(/<link [^>]*js\/foo[^>]*\.js" rel="preload" as="script">/) + // expect(bar).toMatch(/<link [^>]*js\/bar[^>]*\.js" rel="preload" as="script">/) // should prefetch async chunk js and css - expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/) - expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js rel=prefetch>/) + // expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/) + // expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/) // should load correct page js - expect(bar).not.toMatch(/<script [^>]*src=\/js\/index\.\w+\.js>/) - expect(bar).not.toMatch(/<script [^>]*src=\/js\/foo\.\w+\.js>/) - expect(bar).toMatch(/<script [^>]*src=\/js\/bar\.\w+\.js>/) + expect(bar).not.toMatch(/<script [^>]*type="module" src="\/js\/index\.\w+\.js" >/) + expect(bar).not.toMatch(/<script [^>]*type="module" src="\/js\/foo\.\w+\.js" >/) + expect(bar).toMatch(/<script [^>]*type="module" src="\/js\/bar\.\w+\.js">/) // assert pages work const port = await portfinder.getPortPromise() diff --git a/packages/@vue/cli-service/__tests__/proxy.spec.js b/packages/@vue/cli-service/__tests__/proxy.spec.js index 725fcef8e1..cbaff7f9df 100644 --- a/packages/@vue/cli-service/__tests__/proxy.spec.js +++ b/packages/@vue/cli-service/__tests__/proxy.spec.js @@ -1,6 +1,6 @@ jest.setTimeout(30000) -const request = require('request-promise-native') +const fetch = require('node-fetch') const { defaultPreset } = require('@vue/cli/lib/options') const create = require('@vue/cli-test-utils/createTestProject') const serve = require('@vue/cli-test-utils/serveWithPuppeteer') @@ -30,29 +30,22 @@ afterAll(() => { let newId = 1 async function assertProxy (url, title) { - const res = await request({ - url: `${url}posts/1`, - json: true - }) + const res = await fetch(`${url}posts/1`).then(result => result.json()) expect(res.title).toBe(title) // POST newId++ - await request({ - url: `${url}posts`, - json: true, + await fetch(`${url}posts`, { method: 'POST', - body: { + body: JSON.stringify({ id: newId, title: 'new', author: 'test' - } + }), + headers: { 'Content-Type': 'application/json' } }) - const newPost = await request({ - url: `${url}posts/${newId}`, - json: true - }) + const newPost = await fetch(`${url}posts/${newId}`).then(result => result.json()) expect(newPost.title).toBe('new') } diff --git a/packages/@vue/cli-service/__tests__/serve.spec.js b/packages/@vue/cli-service/__tests__/serve.spec.js index 7c48242cf5..a20c612cc5 100644 --- a/packages/@vue/cli-service/__tests__/serve.spec.js +++ b/packages/@vue/cli-service/__tests__/serve.spec.js @@ -6,6 +6,7 @@ const { defaultPreset } = require('@vue/cli/lib/options') const create = require('@vue/cli-test-utils/createTestProject') const serve = require('@vue/cli-test-utils/serveWithPuppeteer') +const sleep = n => new Promise(resolve => setTimeout(resolve, n)) test('serve', async () => { const project = await create('e2e-serve', defaultPreset) @@ -20,7 +21,10 @@ test('serve', async () => { project.write(`src/App.vue`, file.replace(msg, `Updated`)) await nextUpdate() // wait for child stdout update signal try { - await page.waitForXPath('//h1[contains(text(), "Updated")]', { timeout: 60000 }) + await page.waitForFunction(selector => { + const el = document.querySelector(selector) + return el && el.textContent.includes('Updated') + }, { timeout: 60000 }, 'h1') } catch (e) { if (process.env.APPVEYOR && e.message.match('timeout')) { // AppVeyor VM is so slow that there's a large chance this test cases will time out, @@ -33,7 +37,6 @@ test('serve', async () => { } ) }) - test('serve with router', async () => { const project = await create('e2e-serve-router', Object.assign({}, defaultPreset, { plugins: { @@ -45,13 +48,14 @@ test('serve with router', async () => { () => project.run('vue-cli-service serve'), async ({ page, helpers }) => { expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`) - expect(await helpers.hasElement('#nav')).toBe(true) + expect(await helpers.hasElement('nav')).toBe(true) expect(await helpers.hasClass('a[href="#/"]', 'router-link-exact-active')).toBe(true) expect(await helpers.hasClass('a[href="#/about"]', 'router-link-exact-active')).toBe(false) await page.click('a[href="#/about"]') + await sleep(1000) expect(await helpers.getText('h1')).toMatch(`This is an about page`) - expect(await helpers.hasElement('#nav')).toBe(true) + expect(await helpers.hasElement('nav')).toBe(true) expect(await helpers.hasClass('a[href="#/"]', 'router-link-exact-active')).toBe(false) expect(await helpers.hasClass('a[href="#/about"]', 'router-link-exact-active')).toBe(true) } @@ -68,13 +72,14 @@ test('serve with legacy router option', async () => { () => project.run('vue-cli-service serve'), async ({ page, helpers }) => { expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`) - expect(await helpers.hasElement('#nav')).toBe(true) + expect(await helpers.hasElement('nav')).toBe(true) expect(await helpers.hasClass('a[href="/"]', 'router-link-exact-active')).toBe(true) expect(await helpers.hasClass('a[href="/about"]', 'router-link-exact-active')).toBe(false) await page.click('a[href="/about"]') + await sleep(1000) expect(await helpers.getText('h1')).toMatch(`This is an about page`) - expect(await helpers.hasElement('#nav')).toBe(true) + expect(await helpers.hasElement('nav')).toBe(true) expect(await helpers.hasClass('a[href="/"]', 'router-link-exact-active')).toBe(false) expect(await helpers.hasClass('a[href="/about"]', 'router-link-exact-active')).toBe(true) } @@ -113,7 +118,10 @@ test('serve with inline entry', async () => { project.write(`src/App.vue`, file.replace(msg, `Updated`)) await nextUpdate() // wait for child stdout update signal try { - await page.waitForXPath('//h1[contains(text(), "Updated")]', { timeout: 60000 }) + await page.waitForFunction(selector => { + const el = document.querySelector(selector) + return el && el.textContent.includes('Updated') + }, { timeout: 60000 }, 'h1') } catch (e) { if (process.env.APPVEYOR && e.message.match('timeout')) { // AppVeyor VM is so slow that there's a large chance this test cases will time out, @@ -143,7 +151,10 @@ test('serve with no public dir', async () => { project.write(`src/App.vue`, file.replace(msg, `Updated`)) await nextUpdate() // wait for child stdout update signal try { - await page.waitForXPath('//h1[contains(text(), "Updated")]', { timeout: 60000 }) + await page.waitForFunction(selector => { + const el = document.querySelector(selector) + return el && el.textContent.includes('Updated') + }, { timeout: 60000 }, 'h1') } catch (e) { if (process.env.APPVEYOR && e.message.match('timeout')) { // AppVeyor VM is so slow that there's a large chance this test cases will time out, @@ -157,13 +168,16 @@ test('serve with no public dir', async () => { ) }) -test('dart sass', async () => { - const project = await create('test-dart-sass', exports.defaultPreset = { - useConfigFiles: false, - cssPreprocessor: 'dart-sass', - plugins: {} - }) +test('use a single websocket connection for HMR', async () => { + const project = await create('e2e-serve-hmr', defaultPreset) + + await serve( + () => project.run('vue-cli-service serve'), + async ({ helpers, requestUrls }) => { + const msg = `Welcome to Your Vue.js App` + expect(await helpers.getText('h1')).toMatch(msg) - // should build successfully - await project.run('vue-cli-service build') + expect(requestUrls.filter(url => url.includes('ws://')).length).toBe(1) + } + ) }) diff --git a/packages/@vue/cli-service/__tests__/serveVue3.spec.js b/packages/@vue/cli-service/__tests__/serveVue3.spec.js new file mode 100644 index 0000000000..1021497c22 --- /dev/null +++ b/packages/@vue/cli-service/__tests__/serveVue3.spec.js @@ -0,0 +1,38 @@ +const { defaultPreset } = require('@vue/cli/lib/options') +// needs to be outside the workspace, so we reuse the createUpgradableProject functionality here +const create = require('@vue/cli-test-utils/createUpgradableProject') +const serve = require('@vue/cli-test-utils/serveWithPuppeteer') + +jest.setTimeout(300000) + +test('serve with Vue 3', async () => { + const project = await create('e2e-serve-vue-3', Object.assign({}, defaultPreset, { vueVersion: '3' })) + + await serve( + () => project.run('yarn serve'), + async ({ page, nextUpdate, helpers }) => { + const msg = `Welcome to Your Vue.js App` + expect(await helpers.getText('h1')).toMatch(msg) + expect(await page.evaluate(() => window.__VUE__)).toBeDefined() + + // test hot reload + const file = await project.read(`src/App.vue`) + project.write(`src/App.vue`, file.replace(msg, `Updated`)) + await nextUpdate() // wait for child stdout update signal + try { + await page.waitForFunction(selector => { + const el = document.querySelector(selector) + return el && el.textContent.includes('Updated') + }, { timeout: 60000 }, 'h1') + } catch (e) { + if (process.env.APPVEYOR && e.message.match('timeout')) { + // AppVeyor VM is so slow that there's a large chance this test cases will time out, + // we have to tolerate such failures. + console.error(e) + } else { + throw e + } + } + } + ) +}) diff --git a/packages/@vue/cli-service/bin/vue-cli-service.js b/packages/@vue/cli-service/bin/vue-cli-service.js index bb780fa01a..126acecab9 100755 --- a/packages/@vue/cli-service/bin/vue-cli-service.js +++ b/packages/@vue/cli-service/bin/vue-cli-service.js @@ -1,10 +1,9 @@ #!/usr/bin/env node -const semver = require('semver') -const { error } = require('@vue/cli-shared-utils') +const { semver, error } = require('@vue/cli-shared-utils') const requiredVersion = require('../package.json').engines.node -if (!semver.satisfies(process.version, requiredVersion)) { +if (!semver.satisfies(process.version, requiredVersion, { includePrerelease: true })) { error( `You are using Node ${process.version}, but vue-cli-service ` + `requires Node ${requiredVersion}.\nPlease upgrade your Node version.` @@ -19,6 +18,7 @@ const rawArgv = process.argv.slice(2) const args = require('minimist')(rawArgv, { boolean: [ // build + // FIXME: --no-module, --no-unsafe-inline, no-clean, etc. 'modern', 'report', 'report-json', diff --git a/packages/@vue/cli-service/generator/index.js b/packages/@vue/cli-service/generator/index.js index f169395b81..bb4a3f5d6b 100644 --- a/packages/@vue/cli-service/generator/index.js +++ b/packages/@vue/cli-service/generator/index.js @@ -1,51 +1,56 @@ module.exports = (api, options) => { api.render('./template', { - doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript') + doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript'), + useBabel: api.hasPlugin('babel') }) + if (options.vueVersion === '3') { + api.extendPackage({ + dependencies: { + 'vue': '^3.2.13' + } + }) + } else { + api.extendPackage({ + dependencies: { + 'vue': '^2.6.14' + }, + devDependencies: { + 'vue-template-compiler': '^2.6.14' + } + }) + } + api.extendPackage({ scripts: { 'serve': 'vue-cli-service serve', 'build': 'vue-cli-service build' }, - dependencies: { - 'vue': '^2.6.10' - }, - devDependencies: { - 'vue-template-compiler': '^2.6.10' - }, - 'postcss': { - 'plugins': { - 'autoprefixer': {} - } - }, browserslist: [ '> 1%', - 'last 2 versions' + 'last 2 versions', + 'not dead', + ...(options.vueVersion === '3' ? ['not ie 11'] : []) ] }) if (options.cssPreprocessor) { const deps = { sass: { - sass: '^1.19.0', - 'sass-loader': '^8.0.0' - }, - 'node-sass': { - 'node-sass': '^4.12.0', - 'sass-loader': '^8.0.0' + sass: '^1.32.7', + 'sass-loader': '^12.0.0' }, 'dart-sass': { - sass: '^1.19.0', - 'sass-loader': '^8.0.0' + sass: '^1.32.7', + 'sass-loader': '^12.0.0' }, less: { - 'less': '^3.0.4', - 'less-loader': '^5.0.0' + 'less': '^4.0.0', + 'less-loader': '^8.0.0' }, stylus: { - 'stylus': '^0.54.5', - 'stylus-loader': '^3.0.2' + 'stylus': '^0.55.0', + 'stylus-loader': '^6.1.0' } } @@ -54,8 +59,23 @@ module.exports = (api, options) => { }) } + // for v3 compatibility + if (options.router && !api.hasPlugin('router')) { + require('./router')(api, options, options) + } + + // for v3 compatibility + if (options.vuex && !api.hasPlugin('vuex')) { + require('./vuex')(api, options, options) + } + // additional tooling configurations if (options.configs) { api.extendPackage(options.configs) } + + // Delete jsconfig.json when typescript + if (api.hasPlugin('typescript')) { + api.render((files) => delete files['jsconfig.json']) + } } diff --git a/packages/@vue/cli-service/generator/router.js b/packages/@vue/cli-service/generator/router.js new file mode 100644 index 0000000000..9deb162676 --- /dev/null +++ b/packages/@vue/cli-service/generator/router.js @@ -0,0 +1,5 @@ +module.exports = (api, options) => { + require('@vue/cli-plugin-router/generator')(api, { + historyMode: options.routerHistoryMode + }) +} diff --git a/packages/@vue/cli-service/generator/template/_gitignore b/packages/@vue/cli-service/generator/template/_gitignore index 0623b09237..53b0add31a 100644 --- a/packages/@vue/cli-service/generator/template/_gitignore +++ b/packages/@vue/cli-service/generator/template/_gitignore @@ -5,6 +5,8 @@ node_modules /tests/e2e/reports/ selenium-debug.log +chromedriver.log +geckodriver.log <%_ } _%> <%_ if (rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-e2e-cypress']) { _%> @@ -12,6 +14,11 @@ selenium-debug.log /tests/e2e/screenshots/ <%_ } _%> +<%_ if (rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-e2e-webdriverio']) { _%> + +/tests/e2e/logs/ +<%_ } _%> + # local env files .env.local .env.*.local @@ -20,6 +27,7 @@ selenium-debug.log npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* # Editor directories and files .idea diff --git a/packages/@vue/cli-service/generator/template/jsconfig.json b/packages/@vue/cli-service/generator/template/jsconfig.json new file mode 100644 index 0000000000..fc75f22038 --- /dev/null +++ b/packages/@vue/cli-service/generator/template/jsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "<%- options.useBabel ? 'esnext' : 'es5' %>", + "module": "esnext", + "baseUrl": "./", + "moduleResolution": "node", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/packages/@vue/cli-service/generator/template/public/index.html b/packages/@vue/cli-service/generator/template/public/index.html index 06ac04291c..2a4672c1f7 100644 --- a/packages/@vue/cli-service/generator/template/public/index.html +++ b/packages/@vue/cli-service/generator/template/public/index.html @@ -1,15 +1,15 @@ <!DOCTYPE html> -<html lang="en"> +<html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%%= BASE_URL %%>favicon.ico"> - <title><%= rootOptions.projectName %> + <%%= htmlWebpackPlugin.options.title %%>
diff --git a/packages/@vue/cli-service/generator/template/src/App.vue b/packages/@vue/cli-service/generator/template/src/App.vue index 314f333d07..2d5fe75fa3 100644 --- a/packages/@vue/cli-service/generator/template/src/App.vue +++ b/packages/@vue/cli-service/generator/template/src/App.vue @@ -1,4 +1,12 @@ <%_ if (!rootOptions.bare) { _%> @@ -14,7 +23,7 @@ import HelloWorld from './components/HelloWorld.vue' export default { - name: 'app', + name: 'App', components: { HelloWorld } @@ -32,7 +41,7 @@ export default { : `` %>> #app { - font-family: 'Avenir', Helvetica, Arial, sans-serif; + font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; @@ -43,7 +52,7 @@ export default { <%_ } else { _%>