diff --git a/lib/module.js b/lib/module.js index 36d41dd..03acd0e 100755 --- a/lib/module.js +++ b/lib/module.js @@ -1,155 +1,154 @@ -const path = require('path') -const consola = require('consola') -const defu = require('defu') +import { addPluginTemplate } from '@nuxt/kit' +import { resolve } from 'path' +import consola from 'consola' +import defu from 'defu' const logger = consola.withScope('nuxt:axios') -function axiosModule (_moduleOptions) { - const { nuxt } = this - - // Combine options - const moduleOptions = { - ...nuxt.options.axios, - ..._moduleOptions, - ...(nuxt.options.runtimeConfig && nuxt.options.runtimeConfig.axios) - } - - // Default port - const defaultPort = - process.env.API_PORT || - moduleOptions.port || - process.env.PORT || - process.env.npm_package_config_nuxt_port || - (this.options.server && this.options.server.port) || - 3000 - - // Default host - let defaultHost = - process.env.API_HOST || - moduleOptions.host || - process.env.HOST || - process.env.npm_package_config_nuxt_host || - (this.options.server && this.options.server.host) || - 'localhost' - - /* istanbul ignore if */ - if (defaultHost === '0.0.0.0') { - defaultHost = 'localhost' - } - - // Transpile defu (IE11) - if (nuxt.options.build.transpile /* nuxt 1 */) { - nuxt.options.build.transpile.push(({ isClient }) => isClient && 'defu') - } - - // Default prefix - const prefix = process.env.API_PREFIX || moduleOptions.prefix || '/' - - // HTTPS - const https = Boolean(this.options.server && this.options.server.https) - - // Headers - const headers = { - common: { - Accept: 'application/json, text/plain, */*' - }, - delete: {}, - get: {}, - head: {}, - post: {}, - put: {}, - patch: {} - } - - // Support baseUrl alternative - if (moduleOptions.baseUrl) { - moduleOptions.baseURL = moduleOptions.baseUrl - delete moduleOptions.baseUrl - } - if (moduleOptions.browserBaseUrl) { - moduleOptions.browserBaseURL = moduleOptions.browserBaseUrl - delete moduleOptions.browserBaseUrl - } - - // Apply defaults - const options = defu(moduleOptions, { - baseURL: `http://${defaultHost}:${defaultPort}${prefix}`, - browserBaseURL: undefined, - credentials: false, - debug: false, - progress: true, - proxyHeaders: true, - proxyHeadersIgnore: [ - 'accept', - 'cf-connecting-ip', - 'cf-ray', - 'content-length', - 'content-md5', - 'content-type', - 'host', - 'x-forwarded-host', - 'x-forwarded-port', - 'x-forwarded-proto' - ], - proxy: false, - retry: false, - https, - headers - }) - - // ENV overrides - - /* istanbul ignore if */ - if (process.env.API_URL) { - options.baseURL = process.env.API_URL - } - - /* istanbul ignore if */ - if (process.env.API_URL_BROWSER) { - options.browserBaseURL = process.env.API_URL_BROWSER - } - - // Default browserBaseURL - if (typeof options.browserBaseURL === 'undefined') { - options.browserBaseURL = options.proxy ? prefix : options.baseURL - } - - // Normalize options - if (options.retry === true) { - options.retry = {} - } - - // Convert http:// to https:// if https option is on - if (options.https === true) { - const https = s => s.replace('http://', 'https://') - options.baseURL = https(options.baseURL) - options.browserBaseURL = https(options.browserBaseURL) - } - - // globalName - options.globalName = this.nuxt.options.globalName || 'nuxt' - - // Register plugin - this.addPlugin({ - src: path.resolve(__dirname, 'plugin.js'), - fileName: 'axios.js', - options - }) - - // Proxy integration - if (options.proxy) { - this.requireModule([ - '@nuxtjs/proxy', - typeof options.proxy === 'object' ? options.proxy : {} - ]) - } - - // Set _AXIOS_BASE_URL_ for dynamic SSR baseURL - process.env._AXIOS_BASE_URL_ = options.baseURL - - logger.debug(`baseURL: ${options.baseURL}`) - logger.debug(`browserBaseURL: ${options.browserBaseURL}`) +export default async (_moduleOptions, nuxt) => { + + // Combine options + const moduleOptions = { + ...nuxt.options.axios, + ..._moduleOptions, + ...(nuxt.options.runtimeConfig && nuxt.options.runtimeConfig.axios) + } + + // Default port + const defaultPort = + process.env.API_PORT || + moduleOptions.port || + process.env.PORT || + process.env.npm_package_config_nuxt_port || + (nuxt.options.server && nuxt.options.server.port) || + 3000 + + // Default host + let defaultHost = + process.env.API_HOST || + moduleOptions.host || + process.env.HOST || + process.env.npm_package_config_nuxt_host || + (nuxt.options.server && nuxt.options.server.host) || + 'localhost' + + /* istanbul ignore if */ + if (defaultHost === '0.0.0.0') { + defaultHost = 'localhost' + } + + // Transpile defu (IE11) + if (nuxt.options.build.transpile && process.client/* nuxt 1 */) { + nuxt.options.build.transpile.push('defu') + } + + // Default prefix + const prefix = process.env.API_PREFIX || moduleOptions.prefix || '/' + + // HTTPS + const https = Boolean(nuxt.options.server && nuxt.options.server.https) + + // Headers + const headers = { + common: { + Accept: 'application/json, text/plain, */*' + }, + delete: {}, + get: {}, + head: {}, + post: {}, + put: {}, + patch: {} + } + + // Support baseUrl alternative + if (moduleOptions.baseUrl) { + moduleOptions.baseURL = moduleOptions.baseUrl + delete moduleOptions.baseUrl + } + if (moduleOptions.browserBaseUrl) { + moduleOptions.browserBaseURL = moduleOptions.browserBaseUrl + delete moduleOptions.browserBaseUrl + } + + // Apply defaults + const options = defu(moduleOptions, { + baseURL: `http://${defaultHost}:${defaultPort}${prefix}`, + browserBaseURL: undefined, + credentials: false, + debug: false, + progress: true, + proxyHeaders: true, + proxyHeadersIgnore: [ + 'accept', + 'cf-connecting-ip', + 'cf-ray', + 'content-length', + 'content-md5', + 'content-type', + 'host', + 'if-none-match', + 'if-modified-since', + 'x-forwarded-host', + 'x-forwarded-port', + 'x-forwarded-proto' + ], + proxy: false, + retry: false, + https, + headers + }) + + // ENV overrides + + /* istanbul ignore if */ + if (process.env.API_URL) { + options.baseURL = process.env.API_URL + } + + /* istanbul ignore if */ + if (process.env.API_URL_BROWSER) { + options.browserBaseURL = process.env.API_URL_BROWSER + } + + // Default browserBaseURL + if (typeof options.browserBaseURL === 'undefined') { + options.browserBaseURL = options.proxy ? prefix : options.baseURL + } + + // Normalize options + if (options.retry === true) { + options.retry = {} + } + + // Convert http:// to https:// if https option is on + if (options.https === true) { + const https = s => s.replace('http://', 'https://') + options.baseURL = https(options.baseURL) + options.browserBaseURL = https(options.browserBaseURL) + } + + // globalName + options.globalName = nuxt.options.globalName || 'nuxt' + + // Register plugin + addPluginTemplate({ + src: resolve(__dirname, 'plugin.js'), + filename: 'axios.options.mjs', + options + }) + + // Proxy integration + if (options.proxy) { + nuxt.options.proxy = typeof options.proxy === 'object' ? options.proxy : (typeof nuxt.options.proxy === 'object' ? nuxt.options.proxy : {}) + nuxt['__module_container__'].requireModule('@nuxtjs/proxy') + } + + // Set _AXIOS_BASE_URL_ for dynamic SSR baseURL + process.env._AXIOS_BASE_URL_ = options.baseURL + + logger.debug(`baseURL: ${options.baseURL}`) + logger.debug(`browserBaseURL: ${options.browserBaseURL}`) } -module.exports = axiosModule module.exports.meta = require('../package.json') diff --git a/lib/plugin.js b/lib/plugin.js index 09bcd64..ef65e73 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,231 +1,238 @@ +import { defineNuxtPlugin } from '#app' import Axios from 'axios' import defu from 'defu' -<% if (options.retry) { %>import axiosRetry from 'axios-retry'<% } %> +'<% if (options.retry) { %>' +import axiosRetry from 'axios-retry' +'<% } %>' // Axios.prototype cannot be modified const axiosExtra = { - setBaseURL (baseURL) { - this.defaults.baseURL = baseURL - }, - setHeader (name, value, scopes = 'common') { - for (const scope of Array.isArray(scopes) ? scopes : [ scopes ]) { - if (!value) { - delete this.defaults.headers[scope][name]; - continue - } - this.defaults.headers[scope][name] = value + setBaseURL(baseURL) { + this.defaults.baseURL = baseURL + }, + setHeader(name, value, scopes = 'common') { + for (const scope of Array.isArray(scopes) ? scopes : [scopes]) { + if (!value) { + delete this.defaults.headers[scope][name]; + continue + } + this.defaults.headers[scope][name] = value + } + }, + setToken(token, type, scopes = 'common') { + const value = !token ? null : (type ? type + ' ' : '') + token + this.setHeader('Authorization', value, scopes) + }, + onRequest(fn) { + this.interceptors.request.use(config => fn(config) || config) + }, + onResponse(fn) { + this.interceptors.response.use(response => fn(response) || response) + }, + onRequestError(fn) { + this.interceptors.request.use(undefined, error => fn(error) || Promise.reject(error)) + }, + onResponseError(fn) { + this.interceptors.response.use(undefined, error => fn(error) || Promise.reject(error)) + }, + onError(fn) { + this.onRequestError(fn) + this.onResponseError(fn) + }, + create(options) { + return createAxiosInstance(defu(options, this.defaults)) } - }, - setToken (token, type, scopes = 'common') { - const value = !token ? null : (type ? type + ' ' : '') + token - this.setHeader('Authorization', value, scopes) - }, - onRequest(fn) { - this.interceptors.request.use(config => fn(config) || config) - }, - onResponse(fn) { - this.interceptors.response.use(response => fn(response) || response) - }, - onRequestError(fn) { - this.interceptors.request.use(undefined, error => fn(error) || Promise.reject(error)) - }, - onResponseError(fn) { - this.interceptors.response.use(undefined, error => fn(error) || Promise.reject(error)) - }, - onError(fn) { - this.onRequestError(fn) - this.onResponseError(fn) - }, - create(options) { - return createAxiosInstance(defu(options, this.defaults)) - } } // Request helpers ($get, $post, ...) for (const method of ['request', 'delete', 'get', 'head', 'options', 'post', 'put', 'patch']) { - axiosExtra['$' + method] = function () { return this[method].apply(this, arguments).then(res => res && res.data) } + axiosExtra['$' + method] = function () { return this[method].apply(this, arguments).then(res => res && res.data) } } const extendAxiosInstance = axios => { - for (const key in axiosExtra) { - axios[key] = axiosExtra[key].bind(axios) - } + for (const key in axiosExtra) { + axios[key] = axiosExtra[key].bind(axios) + } } const createAxiosInstance = axiosOptions => { - // Create new axios instance - const axios = Axios.create(axiosOptions) - axios.CancelToken = Axios.CancelToken - axios.isCancel = Axios.isCancel - - // Extend axios proto - extendAxiosInstance(axios) - - // Intercept to apply default headers - axios.onRequest((config) => { - config.headers = { ...axios.defaults.headers.common, ...config.headers } - }) - - // Setup interceptors - <% if (options.debug) { %>setupDebugInterceptor(axios) <% } %> - <% if (options.credentials) { %>setupCredentialsInterceptor(axios) <% } %> - <% if (options.progress) { %>setupProgress(axios) <% } %> - <% if (options.retry) { %>axiosRetry(axios, <%= serialize(options.retry) %>) <% } %> - - return axios + // Create new axios instance + const axios = Axios.create(axiosOptions) + axios.CancelToken = Axios.CancelToken + axios.isCancel = Axios.isCancel + axios.isAxiosError = Axios.isAxiosError + + // Extend axios proto + extendAxiosInstance(axios) + + // Intercept to apply default headers + axios.onRequest((config) => { + config.headers = { ...axios.defaults.headers.common, ...config.headers } + }) + + // Setup interceptors + '<% if (options.debug) { %>'; setupDebugInterceptor(axios); '<% } %>' + '<% if (options.credentials) { %>'; setupCredentialsInterceptor(axios); '<% } %>' + '<% if (options.progress) { %>'; setupProgress(axios); '<% } %>' + '<% if (options.retry) { %>'; axiosRetry(axios, JSON.parse('<%= JSON.stringify(options.retry) %>')); '<% } %>' + + return axios } -<% if (options.debug) { %> +'<% if (options.debug) { %>' const log = (level, ...messages) => console[level]('[Axios]', ...messages) const setupDebugInterceptor = axios => { - // request - axios.onRequestError(error => { - log('error', 'Request error:', error) - }) - - // response - axios.onResponseError(error => { - log('error', 'Response error:', error) - }) - axios.onResponse(res => { - log( - 'info', - '[' + (res.status + ' ' + res.statusText) + ']', - '[' + res.config.method.toUpperCase() + ']', - res.config.url) - - if (process.client) { - console.log(res) - } else { - console.log(JSON.stringify(res.data, undefined, 2)) - } - - return res - }) -}<% } %> - -<% if (options.credentials) { %> + // request + axios.onRequestError(error => { + log('error', 'Request error:', error) + }) + + // response + axios.onResponseError(error => { + log('error', 'Response error:', error) + }) + axios.onResponse(res => { + log( + 'info', + '[' + (res.status + ' ' + res.statusText) + ']', + '[' + res.config.method.toUpperCase() + ']', + res.config.url) + + if (process.client) { + console.log(res) + } else { + console.log(JSON.stringify(res.data, undefined, 2)) + } + + return res + }) +} +'<% } %>' + +'<% if (options.credentials) { %>' const setupCredentialsInterceptor = axios => { - // Send credentials only to relative and API Backend requests - axios.onRequest(config => { - if (config.withCredentials === undefined) { - if (!/^https?:\/\//i.test(config.url) || config.url.indexOf(config.baseURL) === 0) { - config.withCredentials = true - } - } - }) -}<% } %> + // Send credentials only to relative and API Backend requests + axios.onRequest(config => { + if (config.withCredentials === undefined) { + if (!/^https?:\/\//i.test(config.url) || config.url.indexOf(config.baseURL) === 0) { + config.withCredentials = true + } + } + }) +} +'<% } %>' -<% if (options.progress) { %> +'<% if (options.progress) { %>' const setupProgress = (axios) => { - if (process.server) { - return - } - - // A noop loading inteterface for when $nuxt is not yet ready - const noopLoading = { - finish: () => { }, - start: () => { }, - fail: () => { }, - set: () => { } - } - - const $loading = () => { - const $nuxt = typeof window !== 'undefined' && window['$<%= options.globalName %>'] - return ($nuxt && $nuxt.$loading && $nuxt.$loading.set) ? $nuxt.$loading : noopLoading - } - - let currentRequests = 0 - - axios.onRequest(config => { - if (config && config.progress === false) { - return + if (process.server) { + return } - currentRequests++ - }) - - axios.onResponse(response => { - if (response && response.config && response.config.progress === false) { - return + // A noop loading inteterface for when $nuxt is not yet ready + const noopLoading = { + finish: () => { }, + start: () => { }, + fail: () => { }, + set: () => { } } - currentRequests-- - if (currentRequests <= 0) { - currentRequests = 0 - $loading().finish() + const $loading = () => { + const $nuxt = typeof window !== 'undefined' && window['$<%= options.globalName %>'] + return ($nuxt && $nuxt.$loading && $nuxt.$loading.set) ? $nuxt.$loading : noopLoading } - }) - axios.onError(error => { - if (error && error.config && error.config.progress === false) { - return - } + let currentRequests = 0 + + axios.onRequest(config => { + if (config && config.progress === false) { + return + } + + currentRequests++ + }) + + axios.onResponse(response => { + if (response && response.config && response.config.progress === false) { + return + } - currentRequests-- + currentRequests-- + if (currentRequests <= 0) { + currentRequests = 0 + $loading().finish() + } + }) - if (Axios.isCancel(error)) { - if (currentRequests <= 0) { - currentRequests = 0 + axios.onError(error => { + if (error && error.config && error.config.progress === false) { + return + } + + currentRequests-- + + if (Axios.isCancel(error)) { + if (currentRequests <= 0) { + currentRequests = 0 + $loading().finish() + } + return + } + + $loading().fail() $loading().finish() - } - return + }) + + const onProgress = e => { + if (!currentRequests || !e.total) { + return + } + const progress = ((e.loaded * 100) / (e.total * currentRequests)) + $loading().set(Math.min(100, progress)) } - $loading().fail() - $loading().finish() - }) + axios.defaults.onUploadProgress = onProgress + axios.defaults.onDownloadProgress = onProgress +} +'<% } %>' + +export default defineNuxtPlugin(ctx => { + // runtimeConfig + const runtimeConfig = ctx.$config && ctx.$config.axios || {} - const onProgress = e => { - if (!currentRequests || !e.total) { - return + // baseURL + const baseURL = process.client + ? (runtimeConfig.browserBaseURL || runtimeConfig.browserBaseUrl || runtimeConfig.baseURL || runtimeConfig.baseUrl || '<%= options.browserBaseURL %>' || '') + : (runtimeConfig.baseURL || runtimeConfig.baseUrl || process.env._AXIOS_BASE_URL_ || '<%= options.baseURL %>' || '') + + // Create fresh objects for all default header scopes + // Axios creates only one which is shared across SSR requests! + // https://github.com/mzabriskie/axios/blob/master/lib/defaults.js + const headers = JSON.parse('<%= JSON.stringify(options.headers) %>') + + const axiosOptions = { + baseURL, + headers } - const progress = ((e.loaded * 100) / (e.total * currentRequests)) - $loading().set(Math.min(100, progress)) - } - - axios.defaults.onUploadProgress = onProgress - axios.defaults.onDownloadProgress = onProgress -}<% } %> - -export default (ctx, inject) => { - // runtimeConfig - const runtimeConfig = ctx.$config && ctx.$config.axios || {} - // baseURL - const baseURL = process.client - ? (runtimeConfig.browserBaseURL || runtimeConfig.browserBaseUrl || runtimeConfig.baseURL || runtimeConfig.baseUrl || '<%= options.browserBaseURL || '' %>') - : (runtimeConfig.baseURL || runtimeConfig.baseUrl || process.env._AXIOS_BASE_URL_ || '<%= options.baseURL || '' %>') - - // Create fresh objects for all default header scopes - // Axios creates only one which is shared across SSR requests! - // https://github.com/mzabriskie/axios/blob/master/lib/defaults.js - const headers = <%= JSON.stringify(options.headers, null, 4) %> - - const axiosOptions = { - baseURL, - headers - } - - <% if (options.proxyHeaders) { %> - // Proxy SSR request headers headers - if (process.server && ctx.req && ctx.req.headers) { - const reqHeaders = { ...ctx.req.headers } - for (const h of <%= serialize(options.proxyHeadersIgnore) %>) { - delete reqHeaders[h] + + '<% if (options.proxyHeaders) { %>' + // Proxy SSR request headers + if (process.server && ctx.ssrContext.req && ctx.ssrContext.req.headers) { + const reqHeaders = { ...ctx.ssrContext.req.headers } + for (const h of '<%= options.proxyHeadersIgnore %>'.split(',')) { + delete reqHeaders[h] + } + axiosOptions.headers.common = { ...reqHeaders, ...axiosOptions.headers.common } } - axiosOptions.headers.common = { ...reqHeaders, ...axiosOptions.headers.common } - } - <% } %> + '<% } %>' - if (process.server) { - // Don't accept brotli encoding because Node can't parse it - axiosOptions.headers.common['accept-encoding'] = 'gzip, deflate' - } + if (process.server) { + // Don't accept brotli encoding because Node can't parse it + axiosOptions.headers.common['accept-encoding'] = 'gzip, deflate' + } - const axios = createAxiosInstance(axiosOptions) + const axios = createAxiosInstance(axiosOptions) - // Inject axios to the context as $axios - ctx.$axios = axios - inject('axios', axios) -} + ctx.vueApp.provide('axios', axios); + ctx.provide('axios', axios); +})