Publish npm Packages With Clean Imports
Publish a package from dist and verify clean imports in a fresh project.
Introduction
30 Second Summary
Sharing reusable code should feel like handing someone a finished tool. If they need to know which internal folder holds the useful files, your packaging details have become their problem.
In this project, you will publish a small public npm package whose import paths hide the build folder. You will compare two package previews before proving the clean path in a fresh project.
What You'll Build
Your final demo is a fresh project running code from your published package through an import path with no dist/ segment.
By the end of this project, you'll have:
- A before-and-after tarball view that shows how publishing a subfolder removes dist/ from every packaged path.
- A rewritten package manifest you can inspect to confirm that its public path fields point directly to compiled files.
- A fresh install test where your own published package resolves through a clean import path. The imported code runs successfully in an empty project.
- Secret Mission: An optional challenge to push your package publishing skills further.
Are there any prerequisites?
You'll need Node.js with npm installed on your laptop. You'll also need a free npm account with 2FA enabled for your scoped package.
Before We Start
Your package needs to be built locally before it reaches the npm registry. Node.js provides the runtime for that build process.
npm handles the package files plus your connection to the registry. This step prepares your workspace plus the publishing account that you use throughout the project.
In this step, get ready to:
- Prepare a terminal plus a code editor for the project.
- Verify that Node.js plus npm are available.
- Prepare your npm account for scoped publishing.
Prepare your workspace
The terminal runs your build plus publishing commands. Your code editor gives you a place to create the package files that those commands use.
- Press Cmd+Space on macOS or the Windows key on Windows.
- Type terminal into the system search bar.
- Press Enter to open your terminal application.
- Open the system search bar again.
- Type the name of your preferred code editor into the search bar.
- Press Enter to open the editor.
You should now see a terminal prompt plus an editor window. Keep both available because you move between them throughout the project.
Get the package toolchain ready
Node.js includes the npm command-line tool. A version response from each command proves that your terminal can reach both tools.
- Check whether Node.js plus npm are ready by running these commands:
node -v
npm -v
What do these commands check?
- The node -v command asks Node.js to print its installed version.
- The npm -v command asks npm to print its installed version.
✔️ I see two version numbers
Good start. Your terminal can reach Node.js plus npm, so the local package toolchain is ready.
ⓧ A command is not found
A missing command means Node.js is unavailable to this terminal. Installing the LTS release also installs npm.
- Visit the official Node.js download page.
- Download the installer labelled LTS for your operating system.
- Run the downloaded installer.
- Complete the installer using its default options.
- Close your terminal application after the installation finishes.
- Open a fresh terminal through your system search bar.
- Verify the installation by running these commands:
node -v
npm -v
What confirms the installation?
Each command prints a version number when the installation is available to your terminal. Seeing both numbers confirms that Node.js plus npm are ready.
Still missing a command?
Close every terminal window before opening a new one. This lets your operating system load the updated command path.
If the command remains unavailable, rerun the LTS installer from the official Node.js download page.
Help me diagnose why Node.js or npm is unavailable after installation.
Prepare npm for publishing
An npm scope is the namespace attached to your account. Your user scope starts with an at sign plus your npm username, which keeps your package name separate from packages owned by other users.
Publishing also requires an authenticated npm account. Your email must be verified before the registry accepts a package.
✔️ I already have an npm account
- Visit the npm website.
- Select Sign In.
- Enter your account details.
- Complete the sign-in prompt.
- Check your email inbox for any outstanding npm verification message.
- Complete the email verification if npm requests it.
Your existing account is ready for the security setup below.
ⓧ I need an npm account
- Open npm's account creation guide.
- Select the npm signup page link in the guide.
- Enter a lowercase username in the Username field.
- Enter your email address in the Email address field.
- Enter a strong password in the Password field.
- Accept the displayed agreements.
- Select Create An Account.
- Verify your email address using the message from npm.
Your verified account now has a user scope that matches your username.
Why enable two-factor authentication?
Two-factor authentication adds a second identity check to your npm account. npm requires this protection or an eligible access token before an account can publish packages.
This security setup can feel fiddly because your browser handles part of the process. Follow each prompt in sequence.
- Select your profile picture in the upper-right corner of the npm website.
- Select Account.
- Find Two-Factor Authentication on the account settings page.
- Select Enable 2FA.
- Enter your current npm password.
- Select Confirm password.
- Choose your preferred security-key method.
- Select Continue.
Protect your recovery codes
Recovery codes can restore access without your security key. Treat them like credentials because each code can unlock your account once.
- Enter a name for your security key.
- Select Add security key.
- Complete the browser security prompt.
- Save the recovery codes in a password manager or another private location.
- Select Go back to settings after the codes are stored.
Your npm account now has the protection required for interactive publishing. The next check connects your terminal to that account.
- Return to the terminal you prepared earlier.
- Start the npm sign-in flow by running this command:
npm login
What does this command do?
The npm login command authenticates the npm command-line tool with your account. Follow the displayed authentication prompt to complete the connection.
Unable to sign in?
Check that you use the same username as your verified npm account. Complete the security-key prompt if two-factor authentication requests it.
Help me troubleshoot npm login for this publishing project.
Before the final check, picture the username you expect npm to return.
- Confirm the signed-in npm account by running this command:
npm whoami
What does this command prove?
The npm whoami command asks the registry which account is connected to your terminal. A username confirms that authentication succeeded.
You should see your npm username in the terminal. That username also identifies the user scope available for your package.
- Record your npm scope here: @your-npm-username.
That is the preparation complete. Your tools can build packages locally, while your authenticated npm scope gives the finished package a public home.
Your publishing workspace is ready. Next up, you create the small package that exposes the difference between build paths plus public import paths.
Create and Build the Package
A public npm package starts with a clear boundary between source code and files prepared for publishing. Its package.json manifest records both sides of that boundary.
In this step, you'll create the package files around one exported value. Then Node.js runs a build that writes dist/index.js.
In this step, get ready to:
- Create the scoped package manifest.
- Add the source module plus its README and license.
- Generate dist/index.js with the build script.
Create the package structure
Keeping the project on your Desktop gives the folder a predictable location. The manifest inside that folder defines the scoped package name and its public entry point.
- Switch back to the terminal from your setup.
- Move to your Desktop by running this command:
cd ~/Desktop
What does this command do?
This moves your terminal to the Desktop. The package folder you create next appears there.
- Create the project folder and move into it by running these commands:
mkdir clean-import-demo
cd clean-import-demo
What do these commands do?
- The first line creates the clean-import-demo folder on your Desktop.
- The second line makes that folder the terminal's current location.
Folder already exists?
Move into the existing clean-import-demo folder if you created it during an earlier attempt. Confirm that it does not contain package files you want to keep.
Ask for help checking the existing folder before you continue.
- Return to the code editor from your setup.
- Use its folder picker to select clean-import-demo on your Desktop.
- Use your editor's new-file control to create package.json inside clean-import-demo.
- Enter your existing npm scope here: @<npm-username>.
- Add the manifest by pasting the following content into package.json:
{
"name": "[[NPM_SCOPE=\"@<npm-username>\"]]/clean-import-demo",
"version": "1.0.0",
"description": "A demonstration package with a clean public import path.",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "node -e \"const fs=require('node:fs'); fs.mkdirSync('dist', { recursive: true }); fs.copyFileSync('src/index.js', 'dist/index.js')\""
}
}
What does this manifest control?
- The name field places the package inside your npm scope.
- The version field starts the package at 1.0.0.
- The main field identifies the default package entry file.
- The exports field exposes that entry file through the package root.
- The files field includes the built directory in the package contents.
- The publishConfig.access setting configures the scoped package for public publishing.
- The build script creates the distribution directory.
- The same script copies the source module into the generated output file.
- Save package.json.
- Confirm that npm reads the scoped package name by running this command:
npm pkg get name
What does this check prove?
This asks npm to read the name field from your saved manifest. A correct result proves that package.json contains valid JSON.
You should see @<npm-username> followed by /clean-import-demo in quotation marks.
Package name not appearing?
Check that every property in package.json has the same commas and quotation marks as the reference. Confirm that your scope begins with the @ character.
Ask for help finding the invalid part of your manifest.
Add the package files
A JavaScript module gives consumers something concrete to import. The README and license travel with the package so its purpose and usage terms remain visible.
- Use your editor's new-folder control to create src inside clean-import-demo.
- Use your editor's new-file control to create index.js inside src.
- Add the source module by pasting this code into src/index.js:
// Expose one small value so consumers can test the package.
export const buildMessage = "Built JavaScript output generated from src/index.js.";
What does this module do?
- The buildMessage constant holds the demonstration value for package consumers.
- The export keyword makes that value available through the package entry point.
- Save src/index.js.
Before you run the build, which directory do you expect the script to create?
- Generate the first build output by running this command:
npm run build
What does the build do?
npm runs the build script from package.json. The script creates dist before writing dist/index.js.
- Refresh your editor's file list.
- Expand the new dist folder.
You'll see dist/index.js inside the project. That visible file proves the build script can generate the package output.
Build command failing?
Confirm that the terminal is inside clean-import-demo. Check that both package.json and src/index.js are saved.
Ask for help tracing the build failure.
The package documents sit at the top level of the project. npm can include them alongside the manifest when the package is prepared for publishing.
- Use your editor's new-file control to create README.md inside clean-import-demo.
- Add the package description by pasting this content into README.md:
Package README describing [[NPM_SCOPE="@<npm-username>"]]/clean-import-demo.
What does this file provide?
The README identifies the package for anyone viewing its contents. It also keeps the scoped name attached to the project documentation.
- Save README.md.
You'll see README.md at the top level of your editor's file list.
- Use your editor's new-file control to create LICENSE inside clean-import-demo.
- Add the license placeholder by pasting this content into LICENSE:
License text for the package.
What does this file provide?
The license file records the package's usage terms. This demonstration uses the exact placeholder required for the project.
- Save LICENSE.
You'll see LICENSE beside README.md and package.json.
Verify the build output
The final check confirms that the build remains repeatable after all package files are present. A repeatable build gives you a fresh dist/index.js whenever the source changes.
Before you rebuild, do you expect the existing output file to remain stale or match the saved source?
- Rebuild the distribution output by running this command:
npm run build
What does this rebuild prove?
The build recreates the distribution directory when needed. It also overwrites dist/index.js with the current contents of src/index.js.
- Refresh your editor's file list.
- Expand dist.
- Select dist/index.js.
You'll see the exported buildMessage value inside dist/index.js. Its text matches the value in src/index.js.
Missing dist/index.js?
Confirm that your editor is showing the clean-import-demo folder from the Desktop. Refresh its file list after the build finishes.
Ask for help checking the source path and build script.
Strong start. Your package now turns src/index.js into dist/index.js on demand.
✔️ Awesome, I've got everything!
Your manifest, source module, package documents, and generated output are ready. Double-check that every file is saved before moving on.
ⓧ I'd like to double check the full code
{
"name": "[[NPM_SCOPE=\"@<npm-username>\"]]/clean-import-demo",
"version": "1.0.0",
"description": "A demonstration package with a clean public import path.",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "node -e \"const fs=require('node:fs'); fs.mkdirSync('dist', { recursive: true }); fs.copyFileSync('src/index.js', 'dist/index.js')\""
}
}
// Expose one small value so consumers can test the package.
export const buildMessage = "Built JavaScript output generated from src/index.js.";
// Expose one small value so consumers can test the package.
export const buildMessage = "Built JavaScript output generated from src/index.js.";
Package README describing [[NPM_SCOPE="@<npm-username>"]]/clean-import-demo.
License text for the package.
Next, you'll preview the package from the project folder and inspect how its build directory appears in the package file list.
Inspect the Root Publish Tarball
Your build now produces dist/index.js from src/index.js. The next question is what npm packages from that working project.
A dry run creates a tarball preview without uploading a release. You will inspect that preview to discover which folder becomes the package root.
In this step, get ready to:
- Trace the current entry points to the built package file.
- Preview the package from the project root without publishing it.
- Inspect the tarball listing for the public location of the built file.
Trace the current package entry points
The main field identifies the package's primary entry point. The exports field controls which entry point consumers can access.
- Switch back to the code editor from earlier.
- Use the editor's file list to select package.json.
- Locate the main path that points to ./dist/index.js.
- Locate the exports path that points to ./dist/index.js.
Both entry points currently target the built file inside dist/. The dry run shows how that local layout translates into the package archive.
Preview the tarball from the project root
The folder where you run the publish command determines the starting point for the package preview. Running it from the existing project lets you test the current layout before anything reaches the registry.
Before you run this, do you think the tarball will preserve dist/ or flatten the built file into the package root?
- Switch back to the terminal from earlier.
- Preview the package from the project root by running this command:
npm publish --dry-run
What does this command do?
- The npm publish command asks npm to prepare the package from the current folder.
- The --dry-run flag keeps the release from being uploaded.
- The generated file list reveals each file's location inside the package archive.
- Scan the generated tarball file list in the terminal output.
- Find the entry for the built JavaScript file.
You will see the built file listed under dist/ as dist/index.js. The project root has become the package root.
That check paid off. You exposed the build folder in the public file layout before publishing anything.
No tarball file list?
- Confirm that the terminal is still inside the package project from earlier.
- Confirm that package.json is present in the folder shown by your code editor.
- Read the final terminal message for the specific check that stopped the preview.
Ask for help with the missing tarball preview.
You have caught the folder leak before publishing. Next up, you will assemble a publish-ready dist/ folder that can become the package root.
Prepare the Publish Folder
Your npm dry-run proved that the current publish shape exposes dist/ to consumers.
A postbuild script makes the publish folder repeatable after every build. It also keeps root-only scripts out of the package consumers receive.
In this step, get ready to:
- Create a preparation script for the publish folder.
- Connect the preparation script to the build lifecycle.
- Compare the original manifest with the publish-ready manifest.
Build the publish preparation script
A publish folder needs the compiled module plus the package information that consumers read. The copied package.json must describe files from the perspective of dist/.
What must the script produce?
The root manifest describes your development project. The copied manifest describes the folder consumers install.
- Copies of README.md plus LICENSE sit beside index.js inside dist/.
- A new dist/package.json starts as a copy of the root manifest.
- The scripts block is absent from the copied manifest.
- Every package path beginning with ./dist/ loses that prefix. The path ./dist/index.js becomes ./index.js.
- The script does not rewrite the root package.json.
- Create scripts/prepare-publish.js inside the existing package folder using your code editor's file sidebar.
You will see the new scripts/ folder beside src/ in the package project.
- Add the preparation logic described above to scripts/prepare-publish.js.
- Save scripts/prepare-publish.js.
- Execute scripts/prepare-publish.js with Node.js in the terminal from earlier.
- Inspect dist/ using your code editor's file sidebar.
You will see index.js, package.json, README.md, plus LICENSE inside dist/.
That is the assembly step working: dist/ now contains the compiled code plus its publish metadata.
Preparation script not creating files?
Check that the script reads the root package.json without rewriting it. Confirm that every destination path points inside dist/.
Help me debug the publish preparation script.
Connect postbuild to the build
The postbuild lifecycle hook runs after npm finishes the existing build script. This keeps the compiled output plus publish metadata in sync.
- Add a postbuild entry to the scripts object in the root package.json that executes scripts/prepare-publish.js with Node.js.
- Save the root package.json.
Before you repeat the build, which extra files do you expect dist/ to contain when the lifecycle finishes? Hold that prediction in mind.
- Repeat the same build command you used earlier in the existing terminal.
The build completes successfully. You will see refreshed copies of package.json, README.md, plus LICENSE beside dist/index.js.
You have now automated the fiddly part. Every successful build refreshes the publish folder.
Build finishes without publish files?
Confirm that postbuild sits inside the root scripts object. Check that it points to scripts/prepare-publish.js.
Help me troubleshoot the postbuild lifecycle.
Compare the two manifests
The final comparison proves that the publish folder has its own path perspective. The root manifest keeps development paths while the copied manifest exposes clean consumer paths.
Before you print them, where do you expect the first path difference to appear? Keep your prediction in mind.
- Print the original root package.json in the terminal.
You will see the build plus postbuild entries inside scripts.
- Print dist/package.json immediately beneath the root manifest.
You will see no scripts block in the copied manifest. Its package path fields begin from ./index.js.
- Compare the main plus exports fields across both printed manifests.
The root fields still point to ./dist/index.js. The copied fields point to ./index.js.
Both manifests keep the package name @<npm-username>/clean-import-demo. Both keep version 1.0.0.
Your dist/ folder now behaves like a complete package root. Next, you will dry-run a publish from that folder to inspect the clean tarball layout.
Verify the Clean Publish Tarball
The prepared dist/package.json now points to ./index.js. That gives npm the clean manifest it needs.
Your earlier project-root dry-run exposed the dist/ folder in the package paths. This check previews the prepared folder as the publish root so you can confirm that prefix has disappeared.
In this step, get ready to:
- Preview the tarball generated from the prepared dist folder.
- Compare the clean listing with the earlier project-root listing.
- Confirm the public package paths contain no dist/ prefix.
Check the publish boundary
A dry-run shows the files npm would place in the package without publishing a release. Passing ./dist makes the prepared folder the package root for this preview.
- Switch back to the terminal from earlier.
- Confirm the terminal prompt is inside the package folder from earlier.
Before you run the preview, do you expect the built module to appear as dist/index.js or index.js?
- Ask npm to preview the prepared publish folder by running this command:
npm publish ./dist --dry-run
What Does This Command Do?
- The ./dist argument selects the prepared folder as the package root.
- The --dry-run flag produces a preview without publishing the package.
- The resulting file list shows the paths that package consumers receive.
You should see listing entries for index.js, package.json, README.md, and LICENSE.
No Tarball Listing?
- Confirm your terminal prompt is inside the package folder from earlier.
- Confirm dist/package.json exists in the prepared folder.
- Return to the previous step if the prepared files are missing.
Help me troubleshoot the missing dry-run listing.
Compare the package paths
The two dry-runs use different package roots. Comparing their paths reveals whether your internal build folder still leaks into the public package.
Before you compare the listings, which prefix do you expect the new preview to remove?
- Return to the project-root dry-run listing from earlier.
- Match its dist/index.js entry to the new index.js entry.
- Check the new listing for package.json, README.md, and LICENSE.
- Scan every new path for a leading dist/ prefix.
The new listing includes index.js, package.json, README.md, and LICENSE at the tarball root. No listed path begins with dist/.
Good work. Your tarball now presents the built module at the package root with clean public paths.
Your dry-run proves the subfolder produces the package shape you want. Next, you will add a guard that stops an accidental publish from the project root.
Add a Publish Guard
Your clean dry run proved that the contents of dist can become the package root. Consumers now get clean paths without seeing your build folder.
One habitual root publish can undo that work. A lifecycle guard turns that risky command into a deliberate stop before the wrong folder ships.
In this step, get ready to:
- Add a lifecycle guard to block accidental root publishing.
- Create the approved publish route for the prepared dist folder.
- Prove that a bare root publish stops with your custom error.
Add the guard scripts
The prepublishOnly lifecycle script runs before a root publish completes. Here it checks a project-defined PUBLISH environment variable.
The pub script is the approved route. It sets the flag before rebuilding the prepared package.
- Switch back to package.json in your code editor.
- Find the existing postbuild entry inside the scripts object.
- Add a comma after the existing postbuild value.
- Insert these entries directly below the postbuild entry:
"postbuild": "node scripts/prepare-publish.js",
"prepublishOnly": "if [ -z $PUBLISH ]; then echo 'ERROR: This package must be published using `npm run pub`' && exit 1; fi",
"pub": "PUBLISH=true npm run build && npm publish ./dist"
What do these scripts protect?
- The prepublishOnly script checks whether PUBLISH has a value.
- An empty PUBLISH value prints your custom error. The shell exits with status 1.
- The pub script sets the required value. It rebuilds the prepared package before publishing.
- The final folder argument targets dist as the package root.
- Save package.json.
- Confirm that both new entries sit inside the scripts object.
Is package.json marked invalid?
- Add the missing comma after the postbuild entry.
- Move both new entries inside the scripts object.
- Check that every script value has matching double quotes.
- Help me fix the publish scripts in my package.json.
✔️ Awesome, I've got everything!
Your saved root manifest now contains the guard plus the approved publish route.
ⓧ I'd like to double check the full code
- Compare the publish-related entries in your scripts object with this reference:
"postbuild": "node scripts/prepare-publish.js",
"prepublishOnly": "if [ -z $PUBLISH ]; then echo 'ERROR: This package must be published using `npm run pub`' && exit 1; fi",
"pub": "PUBLISH=true npm run build && npm publish ./dist"
Test the blocked publish
A bare root publish is the muscle-memory path your guard protects. The lifecycle hook gets control before any registry upload begins.
Before you run this, do you expect the registry upload or your custom message to appear first?
- Test the root publish path by running this command:
npm publish
Why the failure is success
You'll see ERROR: This package must be published using `npm run pub`. The command exits before the package uploads.
That is a useful failure. Your project now protects its clean package shape from an accidental root publish.
Did the custom guard message not appear?
- Save package.json before testing again.
- Check that the lifecycle key is spelled exactly as prepublishOnly.
- Check that prepublishOnly remains inside the root scripts object.
- Help me diagnose why my publish guard did not stop the command.
Your publish workflow now has a safety catch. Next up, you will use the approved route to publish the package before testing its clean import path in a fresh project.
Publish and Test the Package
Your publish guard now blocks accidental releases from the project root. The successful dry run also proves that the prepared package has clean file paths.
This final step publishes version 1.0.0 to the npm registry. A fresh project then proves that another developer can install the package from the registry and run it through its clean import path.
In this step, get ready to:
- Publish version 1.0.0 through the guarded publish script.
- Install the published package in an empty test project.
- Run the installed package with Node.js through its clean import path.
Publish version 1.0.0
Publishing makes your scoped package available for anyone to install. Your dry run already proved that the prepared files have the intended package root.
The npm CLI may ask you to complete your configured two-factor authentication flow before it accepts the release. This security check confirms that the publish request comes from you.
- Capture the username portion of your npm scope here: <npm-username>.
- Publish the prepared package through your guarded script by running:
npm run pub
What does this command do?
The pub script sets the permission expected by your publish guard. It then publishes the prepared dist folder as the package root.
You should see npm complete the publication of your scoped package at version 1.0.0.
- Open the npm package website in your browser.
- Search for your full scoped package name.
- Open the matching package result.
- Confirm that version 1.0.0 is listed.
- Confirm that the package visibility is public.
Package did not publish?
Complete the authentication request if npm asks you to verify the publish. Check that the username in your package scope matches the account currently signed in to npm.
A package version can also fail to publish when that exact version already exists. Confirm that the registry does not already list version 1.0.0 for this package.
Help me troubleshoot publishing my scoped npm package.
Install the package in a clean project
A clean project removes every local shortcut that could make the package appear to work. The installation must download the published files and record your package in a new package.json.
- Set up the test-project folder on your Desktop by running these commands:
cd ~/Desktop
mkdir test-project
cd test-project
What do these commands do?
- The first command moves your terminal to the Desktop.
- The second command creates the separate test-project folder.
- The final command moves your terminal into that folder.
- Create the empty test project's manifest by running:
npm init -y
What does this command do?
The -y option accepts npm's default answers. The command creates package.json without opening an interactive questionnaire.
You should see a message confirming that npm wrote test-project/package.json.
Test project not created?
Check whether a folder named test-project already exists on your Desktop. Remove or rename the old folder before repeating the setup commands.
Help me troubleshoot creating the npm test project.
- Install the published version of your scoped package by running:
npm install @[[NPM_USERNAME="<npm-username>"]]/clean-import-demo@1.0.0
What does this command do?
npm downloads version 1.0.0 from the public registry. It places the package under node_modules and records it as a dependency in test-project/package.json.
You should see npm report that it added the package. The dependency now comes from the published registry version.
Package not found during installation?
Confirm that the package page shows version 1.0.0 with public visibility. Check that the username in the install command matches your npm scope.
Help me troubleshoot installing my published scoped package.
Run the clean import
The decisive test happens inside a file with no connection to your source project. Its JavaScript import names only the published package, so Node.js must follow the rewritten entry path inside the installed manifest.
- Use the code editor's folder picker to switch to the test-project folder on your Desktop.
- Use the code editor's new-file control to create test-import.js inside test-project.
- Add the clean import test by copying this code into test-import.js.
// Import the package through its public name
import('@[[NPM_USERNAME="<npm-username>"]]/clean-import-demo').then((packageModule) => {
// Run the single demonstration export
const exportedDemo = packageModule.default ?? Object.values(packageModule)[0]
const result = typeof exportedDemo === 'function' ? exportedDemo() : exportedDemo
console.log(result)
})
What does this code do?
- The dynamic import asks Node.js to resolve the package through its public scoped name.
- The exportedDemo value selects the package's demonstration export.
- The type check calls a function export. A value export passes directly to the result.
- The final log prints the demonstration result in your terminal.
- Save test-import.js.
Before you run the test, do you think Node.js can resolve the package without any dist segment in the import path?
- Switch back to the terminal from earlier.
- Run the clean import test with this command:
node test-import.js
What proves the package works?
Node.js loads the package through the clean scoped name. The installed manifest resolves that request to the published index.js file.
Your import contains no dist segment. That clean path is the public API your package consumers now use.
You did it. Your terminal prints the package's demonstration result from a fresh registry installation.
Clean import not resolving?
Confirm that the terminal is inside the Desktop's test-project folder. Check that test-import.js uses your exact npm username and contains no dist path segment.
Confirm that the installed package appears in test-project/package.json. Reinstall it after correcting any package-name mismatch.
Help me troubleshoot the clean package import.
✔️ Awesome, I've got everything!
Great. Your saved test file uses the clean scoped package name and prints the published demonstration result.
ⓧ I'd like to double check the full code
// Import the package through its public name
import('@[[NPM_USERNAME="<npm-username>"]]/clean-import-demo').then((packageModule) => {
// Run the single demonstration export
const exportedDemo = packageModule.default ?? Object.values(packageModule)[0]
const result = typeof exportedDemo === 'function' ? exportedDemo() : exportedDemo
console.log(result)
})
Secret mission
Audit the Consumer Package Boundary
Your clean import works. Now audit the installed package from a consumer's perspective to prove that its files, manifest paths, and runtime behavior expose no build-folder details.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep your work available, pause it for later, or retire the test release. Your local files create no ongoing cost. Public package hosting is also free for this project.
Resources you used:
- Public package: @<npm-username>/clean-import-demo version 1.0.0 on the npm registry.
- Local package project: The folder containing src/index.js, scripts/prepare-publish.js, and dist/package.json.
- Clean-import test project: The test-project folder containing the fresh installation test.
Keep everything running
Everything can stay as it is. Choose this option if you want to keep demonstrating the clean import path or preparing future package releases.
- Keep @<npm-username>/clean-import-demo version 1.0.0 publicly available for installation.
- Keep the local package project ready for future builds or releases.
- Keep test-project available for future clean-import checks.
- Retain all three resources without ongoing project costs.
Pause - I'll come back to this later
Nothing in this project runs continuously. Your published package plus local files stay idle until you use them again.
- Leave version 1.0.0 published so existing installations remain available.
- Keep the local package project so the postbuild script plus publish guard are ready when you return.
- Keep the test-project folder as a repeatable check for the clean import path.
Delete - I don't want to use this again
Retiring a public test release can feel permanent. Deprecation preserves the package record while warning future installers that they should not depend on this version.
- Use npm's deprecation workflow for @<npm-username>/clean-import-demo to mark version 1.0.0 as deprecated.
- Add Learning package from a completed project. as the deprecation message.
- Refresh the package page for @<npm-username>/clean-import-demo on npmjs.com.
You should see version 1.0.0 marked as deprecated with your message. This confirms that the public test release is retired.
- Move the package folder containing src/index.js plus scripts/prepare-publish.js to your operating system's trash.
- Move the test-project folder to your operating system's trash.
- Empty your operating system's trash to delete both local project folders permanently.
- Confirm the folder that contained src/index.js is absent from its previous location.
- Confirm the test-project folder is absent from its previous location.
Nice Work!
Nice Work!
You did it! You published a public scoped package to the npm registry. You proved its clean import path in a fresh project.
What you learned:
- Built a repeatable package build pipeline that turns src/index.js into dist/index.js. Prepared dist as a self-contained publish root.
- Compared dry-run tarball listings from the project root and dist. Proved that the publishable package no longer exposes the dist/ prefix.
- Protected the release flow with a publish guard that stops accidental root publishes. Installed the public package in test-project. Ran test-import.js through the clean package path.
- Secret Mission: Took on an optional challenge to push the clean package publishing workflow further.
Ready to quiz yourself?