-
Notifications
You must be signed in to change notification settings - Fork 12
Add ember-mirage blog post #2834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nickschot
wants to merge
10
commits into
master
Choose a base branch
from
nickschot/ember-mirage
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1bf0838
Add ember-mirage blog post
nickschot 314e899
Format
nickschot d465098
Minor improvements
nickschot 22957c5
Move caveats to prerequisites
nickschot 55cfabb
format MD
nickschot e294784
Add section about modern ember/emberisms
nickschot f4bf480
Clarify prerequisites/migration path & webpack
nickschot 5bf30de
Clarify that this is for migration in intro & update pre-requisites t…
nickschot 21f6915
Clarify that this is for migration in intro & update pre-requisites t…
nickschot ae31363
Replace paragraph about Webpack/Classic with reference to ember-impor…
nickschot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| --- | ||
| title: "Migrating to ember-mirage: Modern MirageJS for Vite and Embroider" | ||
| authorHandle: nickschot | ||
| tags: [ember, mirage, mainmatter] | ||
| bio: "Nick Schot" | ||
| description: "Nick Schot explains how to migrate from ember-cli-mirage to a miragejs + ember-mirage setup supported by Vite" | ||
| tagline: "If you've been developing Ember applications with API mocking, you're likely familiar with ember-cli-mirage. ember-cli-mirage has been a corner-stone for writing representative tests that rely on a backend API. It allows you to create focused test scenarios with exactly the data you need for that test. Additionally it can be used to develop frontend features before the API even exists. But there's a problem: ember-cli-mirage doesn't work with Vite! As the Ember ecosystem continues to move toward modern build tooling with Embroider and Vite, we need a solution that brings MirageJS along for the journey. This blog post outlines a path forward to keep your MirageJS setup working in modern Ember applications." | ||
| autoOg: true | ||
| --- | ||
|
|
||
| # MirageJS & ember-mirage | ||
|
|
||
| [MirageJS](https://miragejs.com) is the core library. At some point in the past this was extracted from ember-cli-mirage to its own library for framework independent use. [ember-mirage](https://github.com/bgantzler/ember-mirage) is a set of utilities that brings some of the benefits ember-cli-mirage provided. This means that for basic setup, you may not actually need it, but it provides some features that ember-cli-mirage used to provide that might make migrating a little easier. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| This blog post is intended to be a step during a migration to Vite since we'll make use of `import.meta.glob`. This is a feature provided by Vite to allow glob imports of files. The steps can be taken at any point after, for example, running the [ember-vite-codemod](https://github.com/mainmatter/ember-vite-codemod), or after migrating to Vite manually. | ||
|
|
||
| The steps are outlined from a fresh-app perspective to make it clear what each part of the config does. Most of your actual Mirage related files can stay where they are and like they are. We're mostly going to rewrite the configuration. | ||
|
|
||
| If you're still on a classic build, you can make use of the [ember-import-meta-glob](https://github.com/mainmatter/ember-import-meta-glob) polyfill. This does require that all mirage files are within the `app` folder, rather than the root of the project where it used to be with ember-cli-mirage. Vite can work with either location, so once you're ready to upgrade to Vite you won't need to move things around. | ||
|
|
||
| ## Let's get going | ||
|
|
||
| The first thing we'll do is add MirageJS and ember-mirage as dependencies. | ||
|
|
||
| ``` | ||
| pnpm install -D miragejs ember-mirage | ||
| ``` | ||
|
|
||
| With ember-cli-mirage, all mirage related files lived in a top-level `/mirage` folder. This is still where we'll keep our MirageJS configuration, factories, models and other modules. | ||
|
|
||
| In order to start our new setup we'll create a default server configuration which will serve as the entry point. | ||
|
|
||
| ```javascript | ||
| // mirage/servers/default.js | ||
| import { createServer } from "miragejs"; | ||
|
|
||
| export async function makeServer(config) { | ||
| return createServer({ | ||
| ...config, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| One of the features in which ember-cli-mirage followed ember.js is a lot of files were placed in certain spots by convention. In order to provide a similar setup, we can use the `createConfig` utility provided by ember-mirage to load all our Mirage factories, fixtures, models, serializers and identity managers. We can use Vite's `import.meta.glob` to import all of them at once from their folders. The generated config can then be splatted into the config passed to the `createServer` call. | ||
|
|
||
| ```javascript | ||
| // mirage/servers/default.js | ||
| import { createConfig } from "ember-mirage"; | ||
|
|
||
| const mirageConfig = await createConfig({ | ||
| factories: import.meta.glob("../factories/*"), | ||
| fixtures: import.meta.glob("../fixtures/*"), | ||
| models: import.meta.glob("../models/*"), | ||
| serializers: import.meta.glob("../serializers/*"), | ||
| identityManagers: import.meta.glob("../identity-managers/*"), | ||
| }); | ||
|
|
||
| export async function makeServer(config) { | ||
| return createServer({ | ||
| ...mirageConfig, | ||
| ...config, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| ## Loading ember-data models | ||
|
|
||
| Another feature ember-cli-mirage provided was automatically inferring Mirage models from ember-data models, meaning MirageJS will infer model names and their relationships from the ember-data models. The configuration roughly matches the previous one. Note that `makeServer` accepts an optional `store` parameter to pass an ember-data store instance. When called from the application route during development, we pass the store directly. In tests, the store is looked up automatically from the test context. | ||
|
|
||
| ```javascript | ||
| // mirage/servers/default.js | ||
| import { importEmberDataModels } from "ember-mirage/ember-data"; | ||
| const emberDataModels = import.meta.glob("../../app/models/**/*"); | ||
|
|
||
| export async function makeServer(config, _store) { | ||
| // Look up the store from the test context if not provided | ||
| let store = | ||
| _store ?? | ||
|
nickschot marked this conversation as resolved.
|
||
| (await import("@ember/test-helpers")) | ||
| .getContext() | ||
| .owner.lookup("service:store"); | ||
|
|
||
| return createServer({ | ||
| /* ... */ | ||
|
|
||
| models: { | ||
| ...importEmberDataModels(store, emberDataModels), | ||
| ...config.models, | ||
| }, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| If you want the ability to pass custom models, be sure to also splat `config.models` in the way the above config does. | ||
|
|
||
| ## Defining Mirage routes | ||
|
|
||
| Defining routes hasn't really changed. This happens within the `routes() { … }` function part of the `createServer` configuration object. In practice you'll likely want to define your routes in some folder structure for better manageability. | ||
|
|
||
| ```javascript | ||
| // mirage/servers/default.js | ||
| import { createServer } from "miragejs"; | ||
|
|
||
| export async function makeServer(config) { | ||
| /* ... */ | ||
|
|
||
| return createServer({ | ||
| /* ... */ | ||
| routes() { | ||
| this.get("users", function (schema) { | ||
| return schema.users.all(); | ||
| }); | ||
| }, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| ## Integrating with the test setup | ||
|
|
||
| We've added a lot of configuration, but we don't actually boot MirageJS anywhere. Let's add a test helper that uses our previously created `makeServer` function. We'll also set the `environment` to `test`. This [configuration option](https://miragejs.com/docs/testing/application-tests/#the-test-environment) defaults to `development` which adds a default delay of 50ms to every request. Not something we want for tests! The config is also written in a way that allows you to pass a custom `makeServer` when setting up Mirage in a test. | ||
|
|
||
| ```javascript | ||
| // /tests/helpers/setup-mirage.js | ||
| import { setupMirage as upstreamSetupMirage } from "ember-mirage/test-support"; | ||
| import { makeServer } from "my-app/mirage/servers/default"; | ||
| export function setupMirage(hooks, options) { | ||
| options = options || {}; | ||
| options.createServer = options.makeServer || makeServer; | ||
| upstreamSetupMirage(hooks, { | ||
| ...options, | ||
| config: { | ||
| ...options.config, | ||
| environment: "test", | ||
| }, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| ## Trying it out! | ||
|
|
||
| We have everything we need to test the basics. Below is a quick unit test to verify that everything is working as intended. | ||
|
|
||
| ```javascript | ||
| // tests/unit/example-test.js | ||
| import { module, test } from "qunit"; | ||
| import { setupTest } from "ember-qunit"; | ||
| import { setupMirage } from "../helpers/setup-mirage"; | ||
|
|
||
| module("Unit | Mirage | example tests", function (hooks) { | ||
| setupTest(hooks); | ||
| setupMirage(hooks); | ||
| test("it works!", async function (assert) { | ||
| let mirageUser = this.server.create("user", { name: "Chris" }); | ||
| let store = this.owner.lookup("service:store"); | ||
| let emberDataUser = await store.findRecord("user", mirageUser.id); | ||
| assert.strictEqual(mirageUser.name, emberDataUser.name); | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| ## What about using it in the app itself? | ||
|
|
||
| You may also want to use Mirage during development, for example to work on a feature before the backend API is ready. We can use `@embroider/macros` to conditionally start the Mirage server in a way that ensures it is completely excluded from production builds. | ||
|
|
||
| ```javascript | ||
| // app/routes/application.js | ||
| import Route from "@ember/routing/route"; | ||
| import { isDevelopingApp, isTesting, macroCondition } from "@embroider/macros"; | ||
| import { service } from "@ember/service"; | ||
| import config from "../config/environment"; | ||
|
|
||
| export default class ApplicationRoute extends Route { | ||
| @service store; | ||
|
|
||
| async beforeModel() { | ||
| if (macroCondition(isDevelopingApp() && !isTesting()) && config.useMirage) { | ||
| let { makeServer } = await import("../mirage/servers/default"); | ||
| let server = await makeServer( | ||
| { | ||
| environment: "development", | ||
| scenarios: await import("../mirage/scenarios"), | ||
| }, | ||
| this.store | ||
| ); | ||
| server.logging = true; | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `macroCondition` with `isDevelopingApp() && !isTesting()` ensures the entire block is tree-shaken from the production build. The dynamic `await import()` of the server configuration means none of your Mirage code will be bundled in production either. The additional `config.useMirage` flag gives you a way to toggle Mirage on and off during development via your environment configuration. | ||
|
|
||
| We pass the ember-data `store` to `makeServer` so that Mirage can generate its models from your ember-data models. We also import a scenarios module, which is simply a function that seeds the Mirage database with development data. Finally, enabling `server.logging` will log all intercepted requests and responses to the browser console, which is useful for debugging. | ||
|
|
||
| ## Wrapping up | ||
|
|
||
| Migrating from ember-cli-mirage to ember-mirage requires a bit more manual setup, but the result is a Vite and Embroider compatible MirageJS configuration that stays close to the native MirageJS experience, with ember-mirage providing utilities to make migration easier for situations where there was a reliance on ember-cli-mirage. | ||
|
nickschot marked this conversation as resolved.
|
||
|
|
||
| It's worth stepping back and noticing what's happening here: we're using standard JavaScript features like dynamic `await import()`, top-level `await`, and Vite's `import.meta.glob` instead of legacy Ember-specific magic. The old ember-cli-mirage relied heavily on Ember conventions and build pipeline hooks to auto-discover files. The new setup replaces those "Emberisms" with vanilla JS and standard build tool features. This is only possible thanks to the significant progress the Ember ecosystem has made over the last couple of years with Embroider and the move to Vite. Modern Ember applications are much more aligned with the broader JavaScript ecosystem, making it easier to leverage standard tooling and reducing the framework-specific knowledge needed to be productive. | ||
|
|
||
| For more details, check out the [ember-mirage repository](https://github.com/bgantzler/ember-mirage). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.