-
Notifications
You must be signed in to change notification settings - Fork 37
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
Fix issue where, in some situations, the immediate-invoker helper manager (used when you use resourceFactory
) was not correctly destroying the previous instance of a resource (such as when args change))
#1135
Changes from all commits
b4d7d4a
74cc858
880944d
36daa6c
237a12a
0951cd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
// @ts-ignore | ||
import { createCache, getValue } from '@glimmer/tracking/primitives/cache'; | ||
import { assert } from '@ember/debug'; | ||
import { associateDestroyableChild } from '@ember/destroyable'; | ||
import { associateDestroyableChild, destroy } from '@ember/destroyable'; | ||
// @ts-ignore | ||
import { capabilities as helperCapabilities, invokeHelper, setHelperManager } from '@ember/helper'; | ||
import { dependencySatisfies, importSync, macroCondition } from '@embroider/macros'; | ||
|
@@ -14,9 +14,6 @@ type ResourceFactory<Value = any, Args = any> = (...args: SpreadFor<Args>) => Va | |
|
||
interface State { | ||
cache: ReturnType<typeof invokeHelper>; | ||
fn: any; | ||
args: any; | ||
_?: any; | ||
} | ||
|
||
let setOwner: (context: unknown, owner: Owner) => void; | ||
|
@@ -40,6 +37,7 @@ class ResourceInvokerManager { | |
constructor(protected owner: Owner) {} | ||
|
||
createHelper(fn: ResourceFactory, args: any): State { | ||
let previous: object | undefined; | ||
/** | ||
* This cache is for args passed to the ResourceInvoker/Factory | ||
* | ||
|
@@ -51,12 +49,20 @@ class ResourceInvokerManager { | |
|
||
setOwner(resource, this.owner); | ||
|
||
return invokeHelper(cache, resource); | ||
let result = invokeHelper(cache, resource); | ||
|
||
if (previous) { | ||
destroy(previous); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the fix |
||
} | ||
|
||
previous = result; | ||
|
||
return result; | ||
}); | ||
|
||
setOwner(cache, this.owner); | ||
|
||
return { fn, args, cache, _: getValue(cache) }; | ||
return { cache }; | ||
} | ||
|
||
/** | ||
|
@@ -72,6 +78,9 @@ class ResourceInvokerManager { | |
} | ||
|
||
getDestroyable({ cache }: State) { | ||
/** | ||
* This is the parent cache, from `createHelper` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with nested caches, I had a hard time knowing which thing I was dealing with when, so I added some context |
||
*/ | ||
return cache; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -186,6 +186,7 @@ export function resource<Value>( | |
let internalConfig: InternalFunctionResourceConfig<Value> = { | ||
definition: context as ResourceFunction<Value>, | ||
type: 'function-based', | ||
name: 'Resource', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helps with debug output when errors from Glimmer print to the console |
||
[INTERNAL]: true, | ||
}; | ||
|
||
|
@@ -222,10 +223,19 @@ export function resource<Value>( | |
let internalConfig: InternalFunctionResourceConfig<Value> = { | ||
definition: setup as ResourceFunction<Value>, | ||
type: TYPE, | ||
name: getDebugName(setup), | ||
[INTERNAL]: true, | ||
}; | ||
|
||
setHelperManager(ResourceManagerFactory, internalConfig); | ||
|
||
return wrapForPlainUsage(context, internalConfig); | ||
} | ||
|
||
function getDebugName(obj: object) { | ||
if ('name' in obj) { | ||
return `Resource Function: ${obj.name}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helps with debug output when errors from Glimmer print to the console |
||
} | ||
|
||
return `Resource Function`; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"extends": "@tsconfig/ember/tsconfig.json", | ||
"include": ["src/**/*", "unpublished-development-types/**/*"], | ||
"include": ["src/**/*"], | ||
"glint": { | ||
"environment": [] | ||
}, | ||
|
@@ -42,15 +42,21 @@ | |
"allowImportingTsExtensions": true, | ||
|
||
// require extensions | ||
"module": "Node16", | ||
"moduleResolution": "Node16", | ||
// "module": "Node16", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixes local dev experience |
||
// "moduleResolution": "Node16", | ||
// TODO: enable the above when this package gets to add type=module | ||
"moduleResolution": "bundler", | ||
"module": "esnext", | ||
"target": "esnext", | ||
|
||
// https://www.typescriptlang.org/tsconfig#stripInternal | ||
"stripInternal": true, | ||
|
||
// Build settings | ||
"noEmitOnError": false, | ||
// Just a good thing to do | ||
"isolatedModules": true | ||
"isolatedModules": true, | ||
|
||
"types": ["ember-source/types"] | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const EmberApp = require('ember-cli/lib/broccoli/ember-app'); | ||
|
||
const packageJson = require('./package'); | ||
|
||
module.exports = function (defaults) { | ||
const sideWatch = require('@embroider/broccoli-side-watch'); | ||
let app = new EmberApp(defaults, { | ||
trees: { | ||
app: sideWatch('app', { watching: [path.join(__dirname, '../ember-resources')] }), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. allows me to not have to restart the app when I re-compile the addon (which is an injected dependency) |
||
}, | ||
// Add options here | ||
autoImport: { | ||
watchDependencies: Object.keys(packageJson.dependencies), | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { clearRender,render, settled } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
import { setupRenderingTest } from 'ember-qunit'; | ||
|
||
import { cell, resource, resourceFactory } from 'ember-resources'; | ||
|
||
|
||
module('issues/1134', function(hooks) { | ||
setupRenderingTest(hooks); | ||
|
||
test('it works as a vanilla resource', async function (assert) { | ||
let value = cell(0); | ||
|
||
const Data = resource(({ on }) => { | ||
let arg = value.current; | ||
|
||
assert.step(`setup: ${arg}`); | ||
on.cleanup(() => assert.step(`cleanup: ${arg}`)); | ||
|
||
return 0; | ||
}); | ||
|
||
await render(<template>{{Data}}</template>); | ||
|
||
assert.verifySteps(['setup: 0']); | ||
|
||
value.current = 1; | ||
await settled(); | ||
assert.verifySteps(['setup: 1', 'cleanup: 0']); | ||
|
||
await clearRender(); | ||
assert.verifySteps([`cleanup: ${value.current}`]); | ||
|
||
}); | ||
|
||
test('it works with a Wrapper (resourceFactory)', async function (assert) { | ||
let value = cell(0); | ||
|
||
function Wrapper(arg: number) { | ||
assert.step(`wrapper: ${arg}`); | ||
|
||
return resource(({ on }) => { | ||
assert.step(`setup: ${arg}`); | ||
on.cleanup(() => assert.step(`cleanup: ${arg}`)); | ||
|
||
return 0; | ||
}); | ||
} | ||
|
||
resourceFactory(Wrapper); | ||
|
||
await render(<template>{{Wrapper value.current}}</template>); | ||
|
||
assert.verifySteps(['wrapper: 0', 'setup: 0']); | ||
|
||
value.current = 1; | ||
await settled(); | ||
assert.verifySteps(['wrapper: 1', 'setup: 1', 'cleanup: 0']); | ||
|
||
await clearRender(); | ||
assert.verifySteps([`cleanup: ${value.current}`]); | ||
}); | ||
|
||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were all unused