-
-
Notifications
You must be signed in to change notification settings - Fork 380
feat(middleware): add content
option (options.content.filter
, options.content.transform
)
#336
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
Changes from all commits
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 |
---|---|---|
|
@@ -38,6 +38,7 @@ module.exports = function wrapper(context) { | |
return new Promise(((resolve) => { | ||
handleRequest(context, filename, processRequest, req); | ||
function processRequest() { | ||
let notFound; | ||
try { | ||
let stat = context.fs.statSync(filename); | ||
|
||
|
@@ -60,37 +61,67 @@ module.exports = function wrapper(context) { | |
throw new DevMiddlewareError('next'); | ||
} | ||
} | ||
notFound = false; | ||
} catch (e) { | ||
return resolve(goNext()); | ||
notFound = true; | ||
} | ||
|
||
// server content | ||
let content = context.fs.readFileSync(filename); | ||
content = handleRangeHeaders(content, req, res); | ||
|
||
let contentType = mime.getType(filename); | ||
|
||
// do not add charset to WebAssembly files, otherwise compileStreaming will fail in the client | ||
if (!/\.wasm$/.test(filename)) { | ||
contentType += '; charset=UTF-8'; | ||
const currentRequest = Object.assign({}, { | ||
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.
|
||
url: req.url, | ||
method: req.method, | ||
headers: req.headers, | ||
filename, | ||
notFound | ||
}); | ||
if (context.options && context.options.content && context.options.content.filter) { | ||
if (!context.options.content.filter(currentRequest)) { | ||
return goNext(); | ||
} | ||
} | ||
const defaultTransform = (localRequest, fs) => new Promise((localResolve) => { | ||
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. Why |
||
if (notFound) { | ||
return localResolve(null); | ||
} | ||
fs.readFile(localRequest.filename, (err, content) => { | ||
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. Is it a problem that the readFileSync was replaced by an async one? 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. I can't say to 💯, but async I/O should be preferred. Maybe add a comment that it was changed in case we need to (temp) revert this for some reason :) 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.
|
||
if (err) { | ||
localResolve(null); | ||
} else { | ||
localResolve(content); | ||
} | ||
}); | ||
}); | ||
const optionTransform = (context.options && context.options.content) ? context.options.content.transform : null; | ||
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. hmmm I not sure 💯 here, but something in the direction of const { options, fs } = context
// readFile -> Object { request, content, fs }
const readFile = (request, fs) => new Promise((resolve) => {
...
return { request, content, fs }
})
// transform -> Promise<String|?Buffer>
const transform = (options.content && options.content.transform)
? options.content.transform
: ({ request, content, fs }) => content // default
return readFile(request, fs)
.then(transform)
.then((content) => { ... }) maybe, but definitely open for suggestions |
||
return defaultTransform(currentRequest, context.fs) | ||
.then(content => ((!optionTransform) ? content : optionTransform(currentRequest, content, context.fs))) | ||
.then((content) => { | ||
if (content == null) { | ||
return Promise.reject(); | ||
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. Should we better reject with an if (content == null) {
const err = new Error('<message>')
return Promise.reject(err)
} |
||
} | ||
content = handleRangeHeaders(content, req, res); | ||
let contentType = mime.getType(currentRequest.filename); | ||
|
||
// do not add charset to WebAssembly files, otherwise compileStreaming will fail in the client | ||
if (!/\.wasm$/.test(currentRequest.filename)) { | ||
contentType += '; charset=UTF-8'; | ||
} | ||
|
||
res.setHeader('Content-Type', contentType); | ||
res.setHeader('Content-Length', content.length); | ||
res.setHeader('Content-Type', contentType); | ||
res.setHeader('Content-Length', content.length); | ||
|
||
const { headers } = context.options; | ||
if (headers) { | ||
for (const name in headers) { | ||
if ({}.hasOwnProperty.call(headers, name)) { | ||
res.setHeader(name, context.options.headers[name]); | ||
const { headers } = context.options; | ||
if (headers) { | ||
for (const name in headers) { | ||
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.
|
||
if ({}.hasOwnProperty.call(headers, name)) { | ||
res.setHeader(name, context.options.headers[name]); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// Express automatically sets the statusCode to 200, but not all servers do (Koa). | ||
res.statusCode = res.statusCode || 200; | ||
if (res.send) res.send(content); | ||
else res.end(content); | ||
resolve(); | ||
// Express automatically sets the statusCode to 200, but not all servers do (Koa). | ||
res.statusCode = res.statusCode || 200; | ||
if (res.send) res.send(content); | ||
else res.end(content); | ||
resolve(); | ||
}) | ||
.catch(goNext); | ||
} | ||
})); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,6 +152,114 @@ describe('Server', () => { | |
}); | ||
}); | ||
|
||
describe('filters', () => { | ||
before((done) => { | ||
app = express(); | ||
const compiler = webpack(webpackConfig); | ||
instance = middleware(compiler, { | ||
stats: 'errors-only', | ||
content: { | ||
filter(currentRequest) { | ||
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.
|
||
let result = true; | ||
if (/.js$/.test(currentRequest.filename)) { | ||
result = false; | ||
} | ||
return result; | ||
} | ||
}, | ||
logLevel, | ||
publicPath: '/public/' | ||
}); | ||
app.use(instance); | ||
listen = listenShorthand(done); | ||
}); | ||
after(close); | ||
|
||
it('GET request to bundle file with /.js$/ filtered', (done) => { | ||
request(app).get('/public/bundle.js') | ||
.expect(404, done); | ||
}); | ||
|
||
it('request to image with /.js$/ filtered', (done) => { | ||
request(app).get('/public/svg.svg') | ||
.expect('Content-Type', 'image/svg+xml; charset=UTF-8') | ||
.expect('Content-Length', '4778') | ||
.expect(200, done); | ||
}); | ||
}); | ||
|
||
describe('content transform', () => { | ||
before((done) => { | ||
app = express(); | ||
const compiler = webpack(webpackConfig); | ||
instance = middleware(compiler, { | ||
stats: 'errors-only', | ||
content: { | ||
transform(currentRequest, content, memoryFileSystem) { | ||
return new Promise((resolve, reject) => { | ||
if (currentRequest.url === '/public/') { | ||
resolve(Buffer.from(content.toString().replace(/My/, 'Modified'))); | ||
} else if (currentRequest.url === '/public/001/aliased-bundle.js') { | ||
currentRequest.filename = '/bundle.js'; | ||
currentRequest.notFound = false; | ||
memoryFileSystem.readFile(currentRequest.filename, (err, cont) => { | ||
if (err) { | ||
reject(); | ||
} else { | ||
resolve(cont); | ||
} | ||
}); | ||
} else { | ||
resolve(content); | ||
} | ||
}); | ||
} | ||
}, | ||
logLevel, | ||
publicPath: '/public/' | ||
}); | ||
app.use(instance); | ||
listen = listenShorthand(done); | ||
}); | ||
|
||
after(close); | ||
|
||
it('GET request to bundle file, with transform rules on the /public/ URL', (done) => { | ||
request(app).get('/public/bundle.js') | ||
.expect('Content-Type', 'application/javascript; charset=UTF-8') | ||
// TODO(michael-ciniawsky) investigate the need for this test | ||
.expect('Content-Length', '4631') | ||
.expect(200, /console\.log\('Hey\.'\)/, done); | ||
}); | ||
|
||
it('request to existing image, with transform rules on the /public/ URL', (done) => { | ||
request(app).get('/public/svg.svg') | ||
.expect('Content-Type', 'image/svg+xml; charset=UTF-8') | ||
.expect('Content-Length', '4778') | ||
.expect(200, done); | ||
}); | ||
|
||
it('GET request to bundle file, with transform rules on the /public/ URL', (done) => { | ||
request(app).get('/public/001/aliased-bundle.js') | ||
.expect('Content-Type', 'application/javascript; charset=UTF-8') | ||
.expect('Content-Length', '4631') | ||
.expect(200, /console\.log\('Hey\.'\)/, done); | ||
}); | ||
|
||
it('request to non existing file, with transform rules on the /public/ URL', (done) => { | ||
request(app).get('/public/nope') | ||
.expect('Content-Type', 'text/html; charset=utf-8') | ||
.expect(404, done); | ||
}); | ||
|
||
it('request to directory', (done) => { | ||
request(app).get('/public/') | ||
.expect('Content-Type', 'text/html; charset=UTF-8') | ||
.expect('Content-Length', '16') | ||
.expect(200, /Modified Index\./, done); | ||
}); | ||
}); | ||
|
||
describe('no index mode', () => { | ||
before((done) => { | ||
app = express(); | ||
|
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.
Please elaborate further on the need for this