This article will look at some of the opportunities you’ll have when you pull in your libraries inline rather than leaving them as references to external files. It’s a follow on from the articles on Pull libraries inline to your Apps Script project  and Pulling in Apps Script libraries: how is it done which  you may want to check out first.

Motivation

If you are familiar with Node, you’ll know that modules consumed by your project that you include using yarn or npm, are pulled into the codebase of your project rather than being just a reference to some code in the cloud that could disappear at any time. These series of projects are about taking that approach, but without having to leave the Apps Script IDE to use some other tools.

The opportunities

I’ve already covered the basic methods and workflows in the previously referenced articles, but here’s some ideas for some more advanced opportunities.

Debugging and testing

I write a lot of libraries. That means I have to support and debug them, either without the debugger, or by creating test scripts in the library itself – which isn’t really testing it – so that I can use the debugger to set breakpoints in the library. None of that is ideal, especially when there are all sorts of different versions being used out there.

For most libraries I have a test project that can test their validity, but debugger support for libraries is limited, so I needed a better approach.

By inlining the libraries, and using the versionTreatment: “respect” option, I can recreate the exact environment for fixing reported issues, and use the debugger to set breakpoints in the library code. Since the library code is now temporarily part of my project, the debugger is fully available to figure out what is going on in the libraries, and to test fixes in the inlined copies.

Using the versionTreatment: “head” option, I can inline development versions of the libraries and do initial testing and debugging, again as part of my test project.

We can then commit these changes back to the library code, deploy new version(s), and remove the inlined versions from the test project with the revert() method

Upgrading libraries

The inlined contains a number of options for inlining various versions using the versionTreatment options.

versionTreatment

This describes which version of the referenced libraries to pull, and can take these values

  • respect – Respect the version mentioned in the manifest referencing the library. This is the default.
  • upgrade – Upgrade to the latest deployed version. Note that the deployment record for the new IDE is checked here. It doesn’t have access to libraries  deployed using the old IDE, so if it can’t find a new IDE deployment  it’ll fail with an error like this. If it does, then you could consider doing a sloppyupgrade (see later) as a workaround if you can’t deploy using the new IDE.
    Error: Couldn’t find any deployments for 1v_l4xN3ICa0lAW315NQEzAHPSoNiFdWHsMEwj2qA5t9cgZ5VWci2Qxv2:(cGoa) (upgrade:upgrade to the latest deployed versions) – maybe they were deployed with old IDE?
  • head – Use the latest code of the library whether or not it’s been deployed. This is equivalent to development mode. These will generate console messages like this.
    Modified reference to 1U6j9t_3ONTbhTCvhjwANMcEXeHXr4shgzTG0ZrRnDYLcFl3_IH2b2eAY:(cCacheHandler) from version 18 to head ((head:use the latest version whether or not deployed))
  • sloppyupgrade – The same as upgrade, but if it can find no deployed versions or the deployed version is lower than the one in the manifest it will do a head. This could generate a console message like this
    Sloppy upgrade of reference to library 1dajqLysdKo8IoqddtEaGhtUUlSbtSQ1Agi2K5cXSUm0DxXfLYouSO9yD:(bmRottler) from version 10 to head (use the latest deployed version if there is one – otherwise use the head)

Checking the latest versions

Using the “head” option to pull all the latest versions of all the libraries in the library tree into your test project is a good way to see if they all work with each other before deploying them as latest versions.

Checking for multiple versions

It’s very common for one library to be using version x of a library and a library further down the tree  to be using version y, but you didn’t know it. That means that your project will be using different versions of the same library. Inlining all the libraries with the “respect” option will provide handy console messages about what it’s doing.

You can quickly see by inspecting the _bmlimport/__bmlimporter_gets script file whether you are referencing multiple versions. This example shows that this project references 2 different versions of cUseful library.

var __bmlimporter_gets = {
get __bmLimporter_nQV8_v6 () {
return new __bmLimporter_nQV8_v6 ()
},
get __bmImportScript_IUJH_v6 () {
return new __bmImportScript_IUJH_v6 ()
},
get __bmCrusher_kVG0_v22 () {
return new __bmCrusher_kVG0_v22 ()
},
get __bmUpstash_ZNX__v7 () {
return new __bmUpstash_ZNX__v7 ()
},
get __cGcsStore_qorl_v13 () {
return new __cGcsStore_qorl_v13 ()
},
get __cUseful_41Ex_v130 () {
return new __cUseful_41Ex_v130 ()
},
get __cUseful_41Ex_v129 () {
return new __cUseful_41Ex_v129 ()
},
get __bmAcorn_u1jE_v1 () {
return new __bmAcorn_u1jE_v1 ()
}
}
_bmlimport/__bmlimporter_gets

Perhaps you need that for some compatibility reason. bmLimporter supports multiple versions, so it will work.

More often than not though, you can harmonize the offending library version references to the same version.

runTimeVersion compatibility

When you include a library, it is initialized in the runtime specified in its own manifest. That means that project libraries may be running different environments. There are some issues with legacy code in V8, in particular with additional reserved words. If, for example, you have variable called ‘package’ in code that has been working fine for years, then you choose to use v8 it will fail.

Here’s a list of reserved words to avoid. Similarly, writing v8 specific code to a project that is using the legacy runtime will also fail.

Inlined libraries will all run in the single runtime of the test project, which of course may now throw up previously undetected incompatibility errors. You’ll see this error when you try to commit the inlined files as it originates from the Apps Script API disliking the code. Here’s where the API checks compatibility.

  sapi.addFiles({
scriptId: newScriptId,
clear: true,
files
}).throw()
v8 incompatibilities will be detected here

Always add the .throw() option when writing to the project unless you plan on handling the error yourself, but in any case, the api avoids damage by the detection of such errors before commiting.

Changing the runtimeVersion default

If you really want to stick with the legacy runtime for both your test project and all the inlined libararies, then use the runtimeVersion option like this.


const files = limporter.getInlineProjectFiles({
scriptId,
versionTreatment: 'respect',
runtimeVersion: 'DEPRECATED_ES5'
})
using runtime option

The runtimeVersion option takes these values.  bmLimporter will use ‘V8’ by default.

  • STABLE the default Apps Script runtime (currently Rhino).
  • V8 the V8 powered runtime.
  • DEPRECATED_ES5 the Rhino runtime. Setting this value also prevents automatic migration to V8.

Classes

Classes in libraries are not directly visible to their host projects.

However, you can ask bmLimporter to export classes it finds in libraries too with the exportClasses option, used like this

  const files = limporter.getInlineProjectFiles({ 
scriptId,
versionTreatment: 'respect',
noCache: true,
exportClasses: true
})
exporting classes

You can then reference them in your host project like this

const myClass = new libraryName.MyClass()
accessing classes

Although this might be handy, remember that if you revert to a library version you will lose access to the class definition.

Reverting

You may want to revert to the library version of a project. During import, bmLimporter creates a file called __bmlimport/manifest.json. This is just a copy of the original manifest. Therefore it’s very straightforward to revert to the library version, simply by copying the contents into your appsscript.json, then deleting everything in __bmlimport/*.

The library also provides a .revert() method that does all that for you. Here’s an example of complete reverter script using the revert() method

const reverter = () => {

// create an instance
const limporter = bmLimporter.newLimporter({
tokenService: ScriptApp.getOAuthToken,
fetcher: UrlFetchApp.fetch,
cacheStore: CacheService.getUserCache()
})

// we'll need this later
const {sapi} = limporter

// this is the scriptId of the input project that needs its libraries inlined
// it's my inlined project
const inlinedId = '1fBU_YHk4LpQbuPqYOLlRXsqlP_ECgHAYVdbDa9oPhgwwnTaD0uBwHMRN'

// this is where to write the inlined project - it could be the same as the input scriptId
const revertedId = '1Bq9cO6YpXnTXv0MGqQGAq2nuqAVWE9C8c_vqsYGJx7YSCmo2E3i4FJIC'
console.log('reverting to ', revertedId)

/*
* inline the files
* this example will upgrade all libraries to use latest deployed versions
* also turning off caching - just in case any of the underlying libraries have been redeployed since the last time I ran this - usually not necessary
*/
const files = limporter.revert({
scriptId:inlinedId ,
noCache: true
})


/*
* write the files to the new project
* first clearing out any files that are there for a fresh start
*/
sapi.addFiles({
scriptId: revertedId,
clear: true,
files
}).throw()

}
reverting

Refreshing

To take that one stage further, you may want to simply refresh your project to inline different versions of the libraries. Perhaps because there’s a new deployment or you want to try a different versionTreatment.

You could do that with a revert() followed by a  getInlineProjectFiles(), or you could use the refreshInlineProjectFiles() method.

Here’s an example of a refresher script.

const revertConvert = () => {

// create an instance
const limporter = bmLimporter.newLimporter({
tokenService: ScriptApp.getOAuthToken,
fetcher: UrlFetchApp.fetch,
cacheStore: CacheService.getUserCache()
})

// we'll need this later
const {sapi} = limporter

// this is the scriptId of the input project that currently has imported libraries
const inlinedId = '1Bq9cO6YpXnTXv0MGqQGAq2nuqAVWE9C8c_vqsYGJx7YSCmo2E3i4FJIC'

// this is where to write the updated inlined project
const updatedId = '1Bq9cO6YpXnTXv0MGqQGAq2nuqAVWE9C8c_vqsYGJx7YSCmo2E3i4FJIC'
console.log('reverting', inlinedId,' then updating to ', updatedId)


/*
* revert then refresh hthe library files
* this example will upgrade all libraries to use latest deployed versions
*/
const files = limporter.refreshInlineProjectFiles({
scriptId:inlinedId,
versionTreatment: 'upgrade',
noCache: true,
exportClasses: true
})

/*
* write the files to the new project
* first clearing out any files that are there for a fresh start
*/
sapi.addFiles({
scriptId: updatedId,
clear: true,
files
}).throw()

}
refresh script

Next

Inlining libraries currently only supports server side Apps Script code in libraries – just like the library system in Apps Script. However you can use these techniques to create a library system for pulling in client side html too. This would give developers the chance to distribute and import ready made web app and add-on components.

You can already do this using Import, export and mix container bound and standalone Apps Script projects which has an example of inlining a complete Add-on.

However, if you combine multiple projects containing client side code and markup there might be global space collisions. Taking some manual action to avoid it would be hard to generalize. BmLimporter automates avoidance of collisions with server side library code, but it’s more complex with client side code.

I have some ideas I’m mulling over to overcome some of the gotchas – watch this space for future developments. Ping me with suggestions and offers of collaboration if you want to help to develop this project further.

Links

The library

bmLimporter 18vZDGy1YTRsMSraL2Rj2haSrJzRegIUv2mCgMwDHFu5uobx6chyXnQV8

Example converter, reverter and refresher

testBmLimporter 11ktzauPNsZ2jq2EO4dhO9T0Rs-3qBn_lqwMNZc4osv1Myi7qD7dLd2j2

All are also on github in their respective repos at github.com/brucemcpherson

Also worth reading

Import, export and mix container bound and standalone Apps Script projects.

Inline libraries in an Apps Script project

Pulling in Apps Script libraries – how is it done

Related

[pt_view id=”b889054kv