UNPKG

vue3-sfc-loader

Version:

Vue3 Single File Component loader

294 lines (205 loc) 6.52 kB
const Fs = require('fs'); const Path = require('path'); const puppeteer = require('puppeteer'); const mime = require('mime-types'); const local = new URL('http://local/'); // DEV // 1/ call: DEV=1 yarn run tests // 2/ use test.only( const isDev = !!JSON.parse(process.env.DEV ?? 0); if ( isDev ) jest.setTimeout(1e9); const pendingPages = []; async function createPage({ files, processors= {}}) { async function getFile(url) { const { origin, pathname } = new URL(url); if ( origin !== local.origin ) return null let body = files[pathname] if (processors[pathname]) { body = processors[pathname](body) } const res = { contentType: mime.lookup(Path.extname(pathname)) || '', body, status: files[pathname] === undefined ? 404 : 200, }; return res; } const page = await browser.newPage(); page.setDefaultTimeout(3000); await page.setRequestInterception(true); page.on('request', async interceptedRequest => { try { const file = await getFile(interceptedRequest.url(), 'utf-8'); if (file) { return void interceptedRequest.respond({ ...file, contentType: file.contentType + '; charset=utf-8', }); } interceptedRequest.continue(); } catch (ex) { page.emit('pageerror', ex) } }); const output = []; page.on('console', async msg => { const entry = { type: msg.type(), text: msg.text(), content: await Promise.all( msg.args().map(e => e.jsonValue()) ) }; if ( isDev ) console.log(expect.getState().currentTestName, entry); output.push(entry); } ); page.on('pageerror', error => { // Emitted when an uncaught exception happens within the page. const entry = { type: 'pageerror', text: error.message, content: error }; console.log(expect.getState().currentTestName, entry); output.push(entry); } ); page.on('error', msg => { // Emitted when the page crashes. console.log(expect.getState().currentTestName, 'error', msg); }); await page.goto(new URL('/index.html', local)); await Promise.race([ page.waitForTimeout(350), //page.waitForSelector('#done'), //new Promise(resolve => page.exposeFunction('_done', resolve)), ]); pendingPages.push(page); return { page, output }; } // close all pending pages from previous test beforeEach(async () => { await Promise.all(pendingPages.map(e => e.isClosed() ? undefined : e.close())); pendingPages.length = 0; }); // if dev, suspend on first test afterEach(async () => { if ( isDev ) await new Promise(() => {}); }); let browser; beforeAll(async () => { if ( browser ) return browser; browser = await puppeteer.launch({ headless: !isDev, pipe: true, args: [ '--incognito', '--disable-gpu', '--disable-dev-shm-usage', // for docker '--disable-accelerated-2d-canvas', '--deterministic-fetch', '--proxy-server="direct://"', '--proxy-bypass-list=*', ] }); }); afterAll(async () => { await browser.close(); }); const defaultFilesFactory = ({ vueTarget }) => ({ [`/vue${ vueTarget }-sfc-loader.js`]: Fs.readFileSync(Path.join(__dirname, `../dist/vue${ vueTarget }-sfc-loader.js`), { encoding: 'utf-8' }), '/vue': Fs.readFileSync(Path.join(__dirname, [,,'../node_modules/vue2/dist/vue.runtime.js','../node_modules/vue/dist/vue.global.js'][vueTarget]), { encoding: 'utf-8' }), '/options.js': ` class HttpError extends Error { constructor(url, res) { super('HTTP Error: ' + (res && res.statusCode ? res.statusCode : '(no status code)')); Error.captureStackTrace(this, this.constructor); Object.defineProperties(this, { name: { value: this.constructor.name, }, url: { value: url, }, res: { value: res, }, }); } } const options = { moduleCache: { vue: Vue }, getFile(path) { return fetch(path).then(res => res.ok ? res.text() : Promise.reject(new HttpError(path, res))); }, addStyle(textContent) { const style = Object.assign(document.createElement('style'), { textContent }); const ref = document.head.getElementsByTagName('style')[0] || null; document.head.insertBefore(style, ref); }, log(type, ...args) { console[type](...args); } } export default options; `, '/optionsOverride.js': ` export default () => {}; `, '/boot.js': ` export default ({ options, createApp, mountApp }) => createApp(options).then(app => mountApp(app)); `, '/index.html': ` <!DOCTYPE html> <html><body> <script src="vue"></script> <script src="vue${ vueTarget }-sfc-loader.js"></script> <!-- scripts --> <script type="module"> import boot from '/boot.js' import options from '/options.js' import optionsOverride from '/optionsOverride.js' const { loadModule } = window['vue${ vueTarget }-sfc-loader']; function createApp(options) { switch ( ${ vueTarget } ) { case 3: { return loadModule('/main.vue', options).then((component) => Vue.createApp(component)); } case 2: { return loadModule('/main.vue', options).then((component) => new Vue(component)); } } } function mountApp(app, eltId = 'app') { switch ( ${ vueTarget } ) { case 3: { if ( !document.getElementById(eltId) ) { const parent = document.body; const elt = document.createElement('div'); elt.id = eltId; parent.insertBefore(elt, parent.firstChild); } return app.mount('#' + eltId); } case 2: { const mountElId = eltId + 'Mount'; if ( !document.getElementById(mountElId) ) { const parent = document.body; const appElt = document.createElement('div'); appElt.id = eltId; const mountElt = document.createElement('div'); mountElt.id = mountElId; appElt.insertBefore(mountElt, appElt.firstChild); parent.insertBefore(appElt, parent.firstChild); } return app.$mount('#' + mountElId); } } } optionsOverride(options) boot({ options, createApp, mountApp, Vue }) .then(app => app && (app.$el.parentNode.vueApp = app)); </script> </body></html> `, }); module.exports = { defaultFilesFactory, createPage, }