outputAsync(opts) {
        opts = opts || {};
        if (typeof opts === 'string') opts = { type: opts };

        this._setSheetRefs();

        let definedNamesNode = xmlq.findChild(this._node, "definedNames");

        this._sheets.forEach((sheet, i) => {
            if (!sheet._autoFilter) return;

            if (!definedNamesNode) {
                definedNamesNode = {
                    name: "definedNames",
                    attributes: {},
                    children: []
                };

                xmlq.insertInOrder(this._node, definedNamesNode, nodeOrder);
            }

            xmlq.appendChild(definedNamesNode, {
                name: "definedName",
                attributes: {
                    name: "_xlnm._FilterDatabase",
                    localSheetId: i,
                    hidden: "1"
                },
                children: [sheet._autoFilter.address({ includeSheetName: true, anchored: true })]
            });
        });

        this._sheetsNode.children = [];
        this._sheets.forEach((sheet, i) => {
            const sheetPath = `xl/worksheets/sheet${i + 1}.xml`;
            const sheetRelsPath = `xl/worksheets/_rels/sheet${i + 1}.xml.rels`;
            const sheetXmls = sheet.toXmls();
            const relationship = this._relationships.findById(sheetXmls.id.attributes['r:id']);
            relationship.attributes.Target = `worksheets/sheet${i + 1}.xml`;
            this._sheetsNode.children.push(sheetXmls.id);
            this._zip.file(sheetPath, xmlBuilder.build(sheetXmls.sheet), zipFileOpts);

            const relationshipsXml = xmlBuilder.build(sheetXmls.relationships);
            if (relationshipsXml) {
                this._zip.file(sheetRelsPath, relationshipsXml, zipFileOpts);
            } else {
                this._zip.remove(sheetRelsPath);
            }
        });

        // Set the app security to true if a password is set, false if not.
        // this._appProperties.isSecure(!!opts.password);

        // Convert the various components to XML strings and add them to the zip.
        this._zip.file("[Content_Types].xml", xmlBuilder.build(this._contentTypes), zipFileOpts);
        this._zip.file("docProps/app.xml", xmlBuilder.build(this._appProperties), zipFileOpts);
        this._zip.file("docProps/core.xml", xmlBuilder.build(this._coreProperties), zipFileOpts);
        this._zip.file("xl/_rels/workbook.xml.rels", xmlBuilder.build(this._relationships), zipFileOpts);
        this._zip.file("xl/sharedStrings.xml", xmlBuilder.build(this._sharedStrings), zipFileOpts);
        this._zip.file("xl/styles.xml", xmlBuilder.build(this._styleSheet), zipFileOpts);
        this._zip.file("xl/workbook.xml", xmlBuilder.build(this._node), zipFileOpts);

        // Generate the zip.
        return this._zip.generateAsync({
            type: "nodebuffer",
            compression: "DEFLATE"
        }).then(output => {
            // If a password is set, encrypt the workbook.
            if (opts.password) output = encryptor.encrypt(output, opts.password);

            // Convert and return
            return this._convertBufferToOutput(output, opts.type);
        });
    }
Generates the workbook output.
@param {{}} [opts] Options
@param {string} [opts.type] - The type of the data to return: base64, binarystring, uint8array, arraybuffer, blob, nodebuffer. Defaults to 'nodebuffer' in Node.js and 'blob' in browsers.
@param {string} [opts.password] - The password to use to encrypt the workbook.
@returns {Promise<string|Uint8Array|ArrayBuffer|Blob|Buffer>} The data.