Nx is a smart, fast and extensible build system with first class integrated repo support and powerful integrations.
Nx has a similar design philosophy to Visual Studio Code. VSCode is a powerful text editor, and you can be very productive with it even if you don't install any extensions. The ecosystem of VSCode's extensions though is what can really level up your productivity.
Nx is similar. The core of Nx is generic, simple, and unobtrusive. Nx plugins, although very useful for many projects, are completely optional.
Please spend few minutes reading through this concepts to understand the Mental Modal of Nx.
- Prerequisite
- Getting Started
- Nx Dependency Graph Support
- Core Coding Principals
- Schematics (i.e. code generation)
- Pinia (State management + Library)
- Vuetify (Material Design Framework)
- Vue-Query (Data Fetching Library)
- Internationalization
- PWA (Progressive Web App)
- Builders (i.e. task runners)
- Enforced Project Boundaries
- Linting
- Unit Testing
- Storybook
- Git Hooks
- Modify the Webpack Configuration
- Updating Nx Plus Vue
- Cheat sheet
- NODE - v14.15.5
- NPM - v6.14.11
If you have not already, with the following:
npm install -g nx
npm install
nx serve admin
nx serve public
This will show you how your apps and libraries are dependent on each other in a graphical format
nx dep-graph
Admin application: admin —> core-admin —> [shared-ui, shared-utils]
Public application: public —> core-public —> [shared-ui, shared-utils]
-
Please keep 95% of the code and units under
/libs
folder (code-admin, core-public, etc)./apps
folder should only have build and configuration related files like main.js, App.vue etc. -
Implement sharable features under
shared-ui
andshared-utils
libraries which will be consumed by admin and public applications respectively. -
Avoid importing anything between admin and public applications/libraries to eliminate the risk of bloating. Although Eslint rule is being enforced to avoid accidental imports.
-
shared-ui
andshared-utils
are application independent libraries, meaning it should only contain modular and reusable components without effecting one's design or layout. -
Try to adopt Composition API vs Options API since this project is already on Vue 2.7, doing so will make Vue3 migration simple and easier.
-
Always try to tie your HTTP API functions to respective pinia actions and then use vue-query to fetch those API functions from vue components. This is bring better control, performance, readability and amazing dev tools experience.
-
Please use our automated GIT branching and PR strategy script located at root folder git.sh for creating a new branch, rebasing, and rasing PR urls.
-
Make sure to lint, format, unit test and most importantly rebase/squash your code before raising a PR and delete the stale branches once PR is merged.
HAPPY CODING! 🎉
nx g @nx-plus/vue:app <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your app. |
Options | Default | Description |
---|---|---|
--tags |
- | Tags to use for linting (comma-delimited). |
--directory |
apps |
A directory where the project is placed. |
--style |
css |
The file extension to be used for style files. |
--unitTestRunner |
jest |
Test runner to use for unit tests. |
--e2eTestRunner |
cypress |
Test runner to use for end to end (e2e) tests. |
--routing |
false |
Generate routing configuration. |
--vueVersion |
2 |
The version of Vue.js that you want to start the project with. |
--skipFormat |
false |
Skip formatting files. |
--babel |
false |
Add Babel support. |
nx g @nx-plus/vue:lib <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your library. |
Options | Default | Description |
---|---|---|
--tags |
- | Tags to use for linting (comma-delimited). |
--directory |
libs |
A directory where the project is placed. |
--unitTestRunner |
jest |
Test runner to use for unit tests. |
--skipFormat |
false |
Skip formatting files. |
--skipTsConfig |
false |
Do not update tsconfig.json for development experience. |
--vueVersion |
2 |
The version of Vue.js that you want to start the project with. |
--publishable |
false |
Create a buildable library. |
--babel |
false |
Add Babel support. |
nx g @nx-plus/vue:component <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your component. |
Options | Default | Description |
---|---|---|
--project |
- | Tags to use for linting (comma-delimited). |
--directory |
- | A directory where the component is placed. |
--style |
css |
The file extension to be used for style files. |
Pinia is the new recommended store library for Vue, it allows you to share a state across components/pages similar to Vue's Composition API.
- Devtools support
- A timeline to track actions, mutations
- Stores appear in components where they are used
- Time travel and easier debugging
- Hot module replacement
- Modify your stores without reloading your page
- Keep any existing state while developing
- Plugins: extend Pinia features with plugins
- Proper TypeScript support or autocompletion for JS users
- Server Side Rendering Support
You can see the different between Vuex and Pinia below
Unlike Vuex, mutations are no longer needed with Pinia and Vue 2.7 or higher. Below is a basic setup to define pinia store with composition API. You can also use Options API if you want to.
UI/libs/shared/ui/src/lib/stores/index.ts
import Vue from 'vue';
import { createPinia, PiniaVuePlugin } from 'pinia';
Vue.use(PiniaVuePlugin);
export const pinia = createPinia();
And initialize the store to respective app under /apps
(admin, public).
UI/apps/admin/src/main.ts
import Vue from 'vue';
import AdminApp from './AdminApp.vue';
import { pinia, router } from '@cssa-ccw/core-admin';
Vue.config.productionTip = false;
new Vue({
pinia,
router,
render: h => h(AdminApp),
}).$mount('#app');
Defining the store
import { AliasType } from '@shared-ui/types/defaultTypes';
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useAliasStore = defineStore('alias', () => {
const aliases = ref<AliasType>([]);
const getAliases = computed(() => aliases.value);
function addAlias(payload: addAlias) {
aliases.value.push(payload);
}
return { aliases, getAliases, addAlias };
});
Access the store in vue components like below:
<script setup lang="ts">
import { useAliasStore } from 'stores';
const store = useAliasStore();
store.addAlias(payload);
</script>
Vuetify is a complete UI framework built on top of Vue.js. The goal of vuetify is to provide developers with the tools they need to build rich and engaging user experiences. Unlike other frameworks, Vuetify is designed from the ground up to be easy to learn and rewarding to master with hundreds of carefully crafted components from the Material Design specification.
Vuetify is developed exactly according to Material Design specification with every component meticulously crafted to be modular, responsive, and performant. Customize your application with unique and dynamic Layouts and customize the styles of your components using SASS variables.
import Vue from 'vue'
import Vuetify from 'vuetify'
import '@mdi/font/css/materialdesignicons.css'
import '@shared-ui/assets/vuetify.css'
Vue.use(Vuetify)
export const vuetify = new Vuetify({
icons: {
iconfont: 'mdi',
},
})
Vue-Query is a data fetching library for Vue.js
While most traditional state management libraries are great for working with client state, they are not so great at working with async or server state. This is because server state is totally different. For starters, server state:
- Is persisted remotely in a location you do not control or own
- Requires asynchronous APIs for fetching and updating
- Implies shared ownership and can be changed by other people without your knowledge
- Can potentially become "out of date" in your applications if you're not careful
Once you grasp the nature of server state in your application, even more challenges will arise as you go, for example:
- Caching... (possibly the hardest thing to do in programming)
- Deduping multiple requests for the same data into a single request
- Updating "out of date" data in the background
- Knowing when data is "out of date"
- Reflecting updates to data as quickly as possible
- Performance optimizations like pagination and lazy loading data
- Managing memory and garbage collection of server state
- Memoizing query results with structural sharing
Vue Query is hands down one of the best libraries for managing server state. It works amazingly well out-of-the-box, with zero-config, and can be customized to your liking as your application grows.
Vue Query allows you to defeat and overcome the tricky challenges and hurdles of server state and control your app data before it starts to control you.
On a more technical note, Vue-Query will likely:
- Help you remove many lines of complicated and misunderstood code from your application and replace with just a handful of lines of React Query logic.
- Make your application more maintainable and easier to build new features without worrying about wiring up new server state data sources
- Have a direct impact on your end-users by making your application feel faster and more responsive than ever before.
- Potentially help you save on bandwidth and increase memory performance
Basic Usage Example:
const { data, isLoading, isError } = useQuery(['config'], initialize);
data
is the response from API,isLoading
andisError
are API states andinitialize
is an actual async function which fetches the API.
Vue-I18n is internationalization plugin for Vue.js
language messages are loading through a JSON file (ex: en.json) and vue-i18n module will be initialize it for each app.
export const i18n = new VueI18n({
locale: defaultLocale,
fallbackLocale: 'en',
silentTranslationWarn: true,
messages,
});
{
"hello": "Welcome to Your Vue.js + TypeScript {msg} App"
}
You will have to make any hardcoded labels into i18n translatable string, $t
will map any keys you loaded through JSON file.
<HelloWorld :msg="$t('hello', { msg: 'Admin' })" />
We also have a eslint plugin enabled to make sure we don't keep any stale hardcoded labels around the repo during the development process.
"plugin:@intlify/vue-i18n/recommended"
Progressive Web Apps (PWAs) are web apps that use service workers, manifests, and other web-platform features in combination with progressive enhancement to give users an experience on par with native apps.
Admin and Public facing applications support PWA.
webpack.config.js under each app's root folder
const { GenerateSW } = require("workbox-webpack-plugin");
// Note: vue-plugin-pwa is not being used to create PWA boiler plate due to technical reasons.
module.exports = {
configureWebpack: {
plugins: [new GenerateSW()]
}
};
Making sure the file manifest.json is linked in public/index.html.
<link rel="manifest" href="<%= BASE_URL %>manifest.json">
Followed by adding service worker into main.ts
- Perform Production build
npm run build-admin
- Go to dist folder
cd dist/apps
- Run http-server
npx http-server admin
and verify 'Add to home' button + PWA features through Lighthouse
nx serve <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your app. |
Options | Default | Description |
---|---|---|
--open |
false |
Open browser on server start. |
--copy |
false |
Copy url to clipboard on server start. |
--stdin |
false |
Close when stdin ends. |
--mode |
development |
Specify env mode (default: development). |
--host |
0.0.0.0 |
Specify host (default: 0.0.0.0). |
--port |
8080 |
Specify port (default: 8080). |
--https |
false |
Use https (default: false). |
--public |
- | Specify the public network URL for the HMR client. |
--skipPlugins |
- | Comma-separated list of plugin names to skip for this run. |
--browserTarget |
- | Target to serve. |
--watch |
true |
Watch for changes. |
--publicPath |
/ |
The base URL your application bundle will be deployed at. |
--transpileDependencies |
[] | 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. |
css.requireModuleExtension |
true |
By default, only files that end 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. |
css.extract |
false |
Whether to extract CSS in your components into a standalone CSS file (instead of inlined in JavaScript and injected dynamically). |
css.sourceMap |
false |
Whether to enable source maps for CSS. Setting this to true may affect build performance. |
css.loaderOptions |
{} |
Pass options to CSS-related loaders. |
devServer |
{} |
All options for webpack-dev-server are supported. |
nx build <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your app. |
Options | Default | Description |
---|---|---|
--mode |
development |
Specify env mode (default: development). |
--dest |
- | Specify output directory. |
--clean |
true |
Remove the dist directory before building the project. |
--report |
false |
Generate report.html to help analyze bundle content. |
--reportJson |
false |
Generate report.json to help analyze bundle content. |
--skipPlugins |
- | Comma-separated list of plugin names to skip for this run. |
--watch |
false |
Watch for changes. |
--index |
- | The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path. |
--main |
- | The full path for the main entry point to the app, relative to the current workspace. |
--tsConfig |
- | The full path for the TypeScript configuration file, relative to the current workspace. |
--publicPath |
/ |
The base URL your application bundle will be deployed at. |
--filenameHashing |
false |
Generated static assets contain hashes in their filenames for better caching control. |
--productionSourceMap |
false |
Setting this to false can speed up production builds if you don't need source maps for production. |
--transpileDependencies |
[] | 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. |
css.requireModuleExtension |
true |
By default, only files that end 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. |
css.extract |
false |
Whether to extract CSS in your components into a standalone CSS file (instead of inlined in JavaScript and injected dynamically). |
css.sourceMap |
false |
Whether to enable source maps for CSS. Setting this to true may affect build performance. |
css.loaderOptions |
{} |
Pass options to CSS-related loaders. |
--stdin |
false |
Close when stdin ends. |
nx build <name> [options]
Arguments | Description |
---|---|
<name> |
The name of your library. |
Options | Default | Description |
---|---|---|
--dest |
- | Specify output directory. |
--clean |
true |
Remove the dist directory before building the project. |
--report |
false |
Generate report.html to help analyze bundle content. |
--reportJson |
false |
Generate report.json to help analyze bundle content. |
--skipPlugins |
- | Comma-separated list of plugin names to skip for this run. |
--watch |
false |
Watch for changes. |
--entry |
- | The full path for the main entry point to your library, relative to the current workspace. |
--tsConfig |
- | The full path for the TypeScript configuration file, relative to the current workspace. |
--inlineVue |
false |
Include the Vue module in the final bundle of library. |
--formats |
commonjs,umd,umd-min |
List of output formats for library builds. |
--name |
- | Name for library. |
--filename |
- | File name for output. |
--transpileDependencies |
[] | 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. |
css.requireModuleExtension |
true |
By default, only files that end 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. |
css.extract |
true |
Whether to extract CSS in your components into a standalone CSS file (instead of inlined in JavaScript and injected dynamically). |
css.sourceMap |
false |
Whether to enable source maps for CSS. Setting this to true may affect build performance. |
css.loaderOptions |
{} |
Pass options to CSS-related loaders. |
If you partition your code into well-defined cohesive units, even a small organization will end up with a dozen apps and dozens or hundreds of libs. If all of them can depend on each other freely, chaos will ensue, and the workspace will become unmanageable.
To help with that Nx uses code analysis to make sure projects can only depend on each other's well-defined public API. It also allows you to declaratively impose constraints on how projects can depend on each other.
Nx provides an ‘enforce-module-boundaries’ eslint rule that enforces the public API of projects in the repo. Each project defines its public API in an index.ts (or index.js) file. If another project tries to import a variable from a file deep within a different project, an error will be thrown during linting.
"rules": {
"@nrwl/nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allowCircularSelfDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "@public",
"onlyDependOnLibsWithTags": ["@public", "@core-public", "@shared-ui", "@shared-utils"]
},
{
"sourceTag": "@admin",
"onlyDependOnLibsWithTags": ["@admin", "@core-admin", "@shared-ui", "@shared-utils"]
},
{
"sourceTag": "@core-public",
"onlyDependOnLibsWithTags": ["@core-public", "@shared-ui", "@shared-utils"]
},
{
"sourceTag": "@core-admin",
"onlyDependOnLibsWithTags": ["@core-admin", "@shared-ui", "@shared-utils"]
},
{
"sourceTag": "@shared-ui",
"onlyDependOnLibsWithTags": ["@shared-ui", "@shared-utils"]
},
{
"sourceTag": "@shared-utils",
"onlyDependOnLibsWithTags": ["@shared-utils", "@shared-ui"]
}
]
}
]
}
Located in project.json
"tags": ["@admin"]
nx lint <name> [options]
We use @nrwl/linter
for linting, so the options are as documented here.
nx test <name> [options]
We use @nrwl/jest
for unit testing, so the options are as documented here.
Storybook is an open source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation.
To serve Storybook using this command
nx storybook core-public
nx storybook core-admin
nx storybook shared-ui
To build Storybook using this command
nx build-storybook <name> [project]
Husky and Lint-staged are used to maintain the quality of this project by checking js/ts lint, formatting, style lint and running test cases before your code is pushed over to origin for a PR or CI/CD.
git commit -m "msg"
will trigger pre-commit hook to run linting & formatting on only effected uncommitted 'staged' changes before you commit your changes. Therefore, If an issue is recognized effected files will be un-staged, please fix the issues and stage it before u try to commit it again.
{
"*.{js,jsx,ts,tsx,css,scss,md,vue}": [
"nx format:write --uncommitted"
],
"*.{js,jsx,ts,tsx,vue}": [
"nx affected --target=lint --uncommitted"
],
"*.{css,scss,vue}": [
"nx affected --target=stylelint --uncommitted"
]
}
git push origin
will trigger pre-push hook to run unit tests on effected files to make sure test cases are passing before you push to origin.
nx affected:test --uncommitted
Modify the webpack config by exporting an Object or Function from your project's configure-webpack.js
file.
If your project does not have a
configure-webpack.js
file, then simply add it at the root of your project.
If the value is an Object, it will be merged into the final config using webpack-merge
.
If the value is a function, it will receive the resolved config as the argument. The function can either mutate the config and return nothing, OR return a cloned or merged version of the config.
For more information see the Vue CLI documentation.
Nx Plus Vue provides migrations which help you stay up to date with the latest version of Nx Plus Vue.
Not only do we migrate the version of Nx Plus Vue, but we also update the versions of dependencies which we install such as vue
and vue-router
.
We recommend waiting for Nx Plus Vue to update these dependencies for you as we verify that these versions work together.
All you have to do to update Nx Plus Vue to the latest version is run the following:
nx migrate @nx-plus/vue
nx migrate @nx-plus/vue@version # you can also specify version
This will fetch the specified version of @nx-plus/vue
, analyze the dependencies and fetch all the dependent packages. The process will keep going until the whole tree of dependencies is resolved. This will result in:
package.json
being updatedmigrations.json
being generated
At this point, no packages have been installed, and no other files have been touched.
Now, you can inspect package.json
to see if the changes make sense and install the packages by running npm install
or yarn
.
migrations.json
contains the transformations that must run to prepare the workspace to the newly installed versions of packages. To run all the migrations, invoke:
nx migrate --run-migrations=migrations.json
For any new developers who are adopting Nx workspaces, following cheat sheet should help speed up the development process