I'd like to have advices on how to build a convenient-to-use and tree-shaking optimized build. I'm using rollup to package a UI library made of multiple components.
My architecture is :
/src
/index.js
/components
/index.js
/component1
/index.js
/Comp.vue
...
/componentN
/index.js
/Comp.vue
/directives
/index.js
/directive1
/index.js
...
/directiveN
/index.js
the src/components/index.js
looks like
export { default as Comp1 } from './component1
...
export { default as CompN } from './componentN
the src/directives/index.js
looks like
export { default as Directive1 } from './directive1
...
export { default as DirectiveN } from './directiveN
Each internal index.js
is just a binding for convenience, such as
import Comp from './Comp.vue'
export default Comp`
Finally the src/index.js
will gather all with :
import { * as components } from './components'
import { * as directives } from './directives'
export { components, directives }
When building, the rollup config looks like :
{
input: 'src/index.js',
output: {
file: 'dist/lib.esm.js',
format: 'esm',
}
(of course i'm avoiding all the transpiling uglifying plugins, i think they'd be noise to this issue)
So this build looks nice, and works, but...
import { components } from 'lib'
const { Comp1 } = components
components
object, when only Comp1
is needed.I understand that I should not be the one caring about tree shaking, but rather providing a tree shaking capable library, and that's what this is about. When testing my build with the most simple @vue/cli template, the full library got imported, even @vue/cli claims to have webpack-treeshaking feature enabled out of the box.
I don't mind building separate files instead of one big esm build, but as much as i recall, one file build with tree shaking were possible. My fear of building separate files is that a CompA
could internally need CompB
, and if the user also need CompB
, in that case it could probably be duplicated in the build (as in, one external use version and one internal use version).
I'm clueless on how to proceed to optimize. Any pointer is highly welcomed.
As for now, the only valid solution I could find is to build separately all the files in the same tree structure inside a dist/
folder. I decided to build the files to provide Vue fileformat style blocks without further need for build or configuration from end consumer.
It looks like this after build :
/src
/index.js
/components
/index.js
/component1
/index.js
/Comp.vue
...
/componentN
/index.js
/Comp.vue
/directives
/index.js
/directive1
/index.js
...
/directiveN
/index.js
/dist
/index.js
/components
/index.js
/component1
/index.js
...
/componentN
/index.js
/directives
/index.js
/directive1
/index.js
...
/directiveN
/index.js
I created a small recursive function to find all 'index.js' and used this list with rollup multi entrypoint feature. hopefully, rollup creates all subfolders so there's no need for checks or mkdir -p
.
// shorthand utility for 'find all files recursive matching regexp (polyfill for webpack's require.context)'
const walk = (directory, regexp) => {
let files = readdirSync(directory)
if (directory.includes('/examples'))
return []
return files.reduce((arr, file) => {
let path = `${directory}/${file}`
let info = statSync(path)
if (info.isDirectory())
return arr.concat(walk(path, regexp))
else if (regexp.test(file))
return arr.concat([path])
else
return arr
}, [])
}
// ...
const esm = walk(`${__dirname}/src`, /^index\.js$/)
.map(file => ({
input: file,
output: {
format: 'esm',
file: file.replace(`${__dirname}/src`, CONFIG.module)
},
...
}))
The last part of the process is to copy/paste package.json
into dist/
, cd
into it and npm publish
from it... This was integrated into our CI tasks, as it's not directly related to rollup or build, but rather publishing.
It's not perfect, but it's the only way I found due to lack of inputs. I hope it'll help someone.